2
0
Эх сурвалжийг харах

some more work on error handling (try/catch still doesn't work)

Dan Korostelev 4 жил өмнө
parent
commit
b823e72cc4

+ 87 - 19
src/optimization/analyzerTexprTransformer.ml

@@ -758,9 +758,23 @@ and block_to_texpr_coroutine ctx bb vcontinuation vresult verror p =
 	declare_var ctx.graph vresult bb;
 	declare_var ctx.graph verror bb;
 
+	let eerror = make_local verror null_pos in
+
+	let mk_int i = make_int com.basic i null_pos in
+
+	let mk_assign estate eid =
+		mk (TBinop (OpAssign,estate,eid)) eid.etype null_pos
+	in
+
 	let vstate = alloc_var VGenerated "_hx_state" com.basic.tint p in
 	declare_var ctx.graph vstate bb;
 	let estate = make_local vstate p in
+	let set_state id = mk_assign estate (mk_int id) in
+
+	let vexcstate = alloc_var VGenerated "_hx_exceptionState" com.basic.tint p in
+	declare_var ctx.graph vexcstate bb;
+	let eexcstate = make_local vexcstate p in
+	let set_excstate id = mk_assign eexcstate (mk_int id) in
 
 	let tstatemachine = tfun [t_dynamic] com.basic.tvoid in
 	let vstatemachine = alloc_var VGenerated "_hx_stateMachine" tstatemachine p in
@@ -781,14 +795,20 @@ and block_to_texpr_coroutine ctx bb vcontinuation vresult verror p =
 		mk (TCall (econtinuation, [make_null t_dynamic p; eerror])) com.basic.tvoid p
 	in
 
-	(* TODO: maybe merge this into block_to_texpr somehow, and only introduce new states when there is a suspension point *)
+	let exc_cases = ref [] in
 
+	(* TODO: maybe merge this into block_to_texpr somehow, and only introduce new states when there is a suspension point *)
 	print_endline "---";
-	let rec loop bb state_id back_state_id statecases current_el while_loop =
+	let rec loop ?(exc_state=None) bb state_id back_state_id statecases current_el while_loop =
 		let p = bb.bb_pos in
 		(* TODO: only do this in the end, avoid unnecessary List.rev *)
 		let el = DynArray.to_list bb.bb_el in
-		let set_state id = mk (TBinop (OpAssign,estate,make_int com.basic id p)) com.basic.tint p in
+
+		let el = match exc_state with
+			| Some id -> set_excstate id :: el
+			| None -> el
+		in
+
 		let ereturn = mk (TReturn None) com.basic.tvoid p in
 
 		let mk_case el = [make_int com.basic state_id p], mk (TBlock el) com.basic.tvoid p in
@@ -842,7 +862,8 @@ and block_to_texpr_coroutine ctx bb vcontinuation vresult verror p =
 			| TermNone ->
 				mk_case (current_el @ el @ [set_state back_state_id]) :: statecases
 			| TermThrow (e,p) ->
-				mk_case (current_el @ el @ [set_state (-1); mk_continuation_call_error e p; ereturn]) :: statecases
+				let ethrow = mk (TThrow e) t_dynamic p in
+				mk_case (current_el @ el @ [ethrow]) :: statecases
 			| TermCondBranch _ ->
 				die "unexpected TermCondBranch" __LOC__)
 
@@ -915,11 +936,33 @@ and block_to_texpr_coroutine ctx bb vcontinuation vresult verror p =
 			let statecases = loop bb_next next_state_id back_state_id statecases [] while_loop in
 			mk_case (current_el @ el @ [set_state body_state_id]) :: statecases
 
-		| SETry (_,_,_,_,p) ->
-			Error.error "try/catch is currently not supported in coroutines" p
+		| SETry (bb_try,_,catches,bb_next,p) ->
+			let try_state_id = get_next_state_id () in
+			let new_exc_state_id = get_next_state_id () in
+			let next_state_id = get_next_state_id () in
+			print_endline (Printf.sprintf "try cur:%d,try:%d,catch:%d,next:%d,back:%d" state_id try_state_id new_exc_state_id next_state_id back_state_id);
+			let statecases = loop bb_try try_state_id next_state_id statecases [] while_loop ~exc_state:(Some new_exc_state_id) in
+			let catch_case =
+				let erethrow = mk (TThrow eerror) t_dynamic null_pos in
+(* 				let eif = List.fold_left (fun acc (v,bb) ->
+					failwith "TODO: need to rework loop to return el instead of cases"
+				) erethrow catches in *)
+				let eif = erethrow in
+				(new_exc_state_id, eif)
+			in
+			exc_cases := catch_case :: !exc_cases;
+			let statecases = loop bb_next next_state_id back_state_id statecases [] while_loop in
+			mk_case (current_el @ el @ [set_state try_state_id]) :: statecases
 	in
 	let statecases = loop bb (get_next_state_id ()) (-1) [] [] None in
 
+	let rethrow_state_id = get_next_state_id () in
+	let statecases = statecases @ List.map (fun (id, e) ->
+		let epattern = mk_int id in
+		let ebody = mk (TBlock [set_excstate rethrow_state_id; e]) com.basic.tvoid null_pos in
+		([epattern],ebody)
+	) !exc_cases in
+
 	(* TODO: this (and the coroutine transform in general) should probably be run before captured vars handling *)
 	(* very ugly, but seems to work: extract locals that are used across states *)
 	let var_usages = Hashtbl.create 5 in
@@ -985,36 +1028,61 @@ and block_to_texpr_coroutine ctx bb vcontinuation vresult verror p =
 	(* TODO:
 		we can optimize while and switch in some cases:
 		 - if there's only one state (no suspensions) - don't wrap into while/switch, don't introduce state var
-		 - if there's no non-suspending state changes, we don't need while, because all suspensions exit the function with return
 	*)
-	let ethrow = mk (TThrow (make_string com.basic "Invalid coroutine state" p)) com.basic.tvoid p in
+
+	let rethrow_case = ([mk_int rethrow_state_id], mk (TThrow eerror) com.basic.tvoid null_pos) in
+	let statecases = statecases @ [rethrow_case] in
+
+	let ethrow = mk (TBlock [
+		set_state rethrow_state_id;
+		mk (TThrow (make_string com.basic "Invalid coroutine state" p)) com.basic.tvoid p
+	]) com.basic.tvoid null_pos
+	in
+
 	let eswitch = mk (TSwitch (estate, statecases, Some ethrow)) com.basic.tvoid p in
-	let eloop = mk (TWhile (make_bool com.basic true p, eswitch, DoWhile)) com.basic.tvoid p in
 
-	(* TODO: this has to be much more complicated, unfortunately, we need a try/catch around the state machine to catch errors from
-	   synchronous throws and then we need to propagate properly and then we need to support try/catch inside coroutines etc etc.
-	   maybe while implementing support for all this, we can as well look into adding COROUTINE_SUSPEND markers and separate coroutine
-	   and non-coroutine worlds a bit more *)
-	let eerror = make_local verror p in
+	let etry = mk (TTry (
+		eswitch,
+		[
+			let vcaught = alloc_var VGenerated "e" t_dynamic null_pos in
+			declare_var ctx.graph vcaught bb;
+			(vcaught, mk (TIf (
+				mk (TBinop (OpEq, estate, mk_int rethrow_state_id)) com.basic.tbool null_pos,
+				mk (TBlock [
+					mk_assign eexcstate (mk_int rethrow_state_id);
+					mk_continuation_call_error (make_local vcaught null_pos) null_pos;
+					mk (TReturn None) com.basic.tvoid null_pos;
+				]) com.basic.tvoid null_pos,
+				Some (mk (TBlock [
+					mk_assign estate eexcstate;
+					mk_assign eerror (make_local vcaught null_pos);
+				]) com.basic.tvoid null_pos)
+			)) com.basic.tvoid null_pos)
+		]
+	)) com.basic.tvoid null_pos in
+
+	let eloop = mk (TWhile (make_bool com.basic true p, etry, DoWhile)) com.basic.tvoid p in
+
 	let eif = mk (TIf (
 		mk (TBinop (
 			OpNotEq,
 			eerror,
-			make_null verror.v_type p (* TODO: throw null should work *)
+			make_null verror.v_type p
 		)) com.basic.tbool p,
-		mk_continuation_call_error eerror p,
-		Some eloop
+		mk_assign estate eexcstate,
+		None
 	)) com.basic.tvoid p in
 
 	let estatemachine_def = mk (TFunction {
 		tf_args = [(vresult,None); (verror,None)];
 		tf_type = com.basic.tvoid;
-		tf_expr = eif;
+		tf_expr = mk (TBlock [eif; eloop]) com.basic.tvoid null_pos
 	}) tstatemachine p in
 
 	let state_var = mk (TVar (vstate, Some (make_int com.basic 0 p))) com.basic.tvoid p in
+	let excstate_var = mk (TVar (vexcstate, Some (make_int com.basic rethrow_state_id p))) com.basic.tvoid p in
 	let shared_vars = List.map (fun v -> mk (TVar (v,None)) com.basic.tvoid null_pos) decls in
-	let shared_vars = List.rev (state_var :: shared_vars) in
+	let shared_vars = List.rev (excstate_var :: state_var :: shared_vars) in
 
 	mk (TBlock (shared_vars @ [
 		mk (TVar (vstatemachine, Some estatemachine_def)) com.basic.tvoid p;

+ 29 - 0
tests/misc/coroutines/src/TestJsPromise.hx

@@ -1,3 +1,4 @@
+import js.lib.Error;
 import js.lib.Promise;
 
 @:coroutine
@@ -45,4 +46,32 @@ class TestJsPromise extends utest.Test {
 			async.done();
 		});
 	}
+
+	function testAwaitRejected(async:Async) {
+		var p = Promise.reject("oh no");
+
+		@:coroutine function awaiting() {
+			var x = await(p);
+			return x + 1;
+		}
+
+		awaiting.start((result,error) -> {
+			Assert.equals("oh no", error);
+			async.done();
+		});
+	}
+
+	function testThrowInPromise(async:Async) {
+		var p = promise(() -> throw new Error("oh no"));
+		p.then(
+			function(result) {
+				Assert.fail();
+			},
+			function(error) {
+				Assert.isOfType(error, Error);
+				Assert.equals("oh no", (error : Error).message);
+				async.done();
+			}
+		);
+	}
 }