diff --git a/.gitignore b/.gitignore
index 1148a80b950..7a3fb70a367 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,3 +133,4 @@ lib.sexp
src/compiler/version.ml
tests/party
tests/misc/projects/Issue10863/error.log
+tests/misc/coroutines/dump
diff --git a/README.md b/README.md
index 5806703c00d..1f6016317ad 100644
--- a/README.md
+++ b/README.md
@@ -1,99 +1,9 @@
-
-
-
-
-
-
-
-
-
-
-
-#
-
-Haxe is an open source toolkit that allows you to easily build cross-platform tools and applications that target many mainstream platforms. The Haxe toolkit includes:
-
- * **The Haxe programming language**, a modern, high-level, strictly-typed programming language
- * **The Haxe cross-compiler**, a state-of-the-art, lightning-speed compiler for many targets
- * **The Haxe standard library**, a complete, cross-platform library of common functionality
-
-Haxe allows you to compile for the following targets:
-
- * JavaScript
- * C++
- * JVM
- * Lua
- * PHP 7
- * Python 3
- * [HashLink](https://hashlink.haxe.org/)
- * [NekoVM](https://nekovm.org/)
- * Flash (SWF Bytecode)
- * And its own [interpreter](https://haxe.org/blog/eval/)
-
-You can try Haxe directly from your browser at [try.haxe.org](https://try.haxe.org)!
-
-For more information about Haxe, head to the [official Haxe website](https://haxe.org).
-
-## License
-
-The Haxe project has several licenses, covering different parts of the projects.
-
- * The Haxe compiler is released under the GNU General Public License version 2 or any later version.
- * The Haxe standard library is released under the MIT license.
- * The Neko virtual machine is released under the MIT license. Its bundled runtime libraries (ndll) and tools are released under open source licenses as described in https://github.com/HaxeFoundation/neko/blob/master/LICENSE
-
-For the complete Haxe licenses, please see https://haxe.org/foundation/open-source.html or [extra/LICENSE.txt](extra/LICENSE.txt).
-
-## Installing Haxe
-
-The latest stable release is available at [https://haxe.org/download/](https://haxe.org/download/). Pre-built binaries are available for your platform:
-
- * **[Windows installer](https://haxe.org/download/file/latest/haxe-latest-win.exe/)**
- * **[Windows binaries](https://haxe.org/download/file/latest/haxe-latest-win.zip/)**
- * **[OSX installer](https://haxe.org/download/file/latest/haxe-latest-osx-installer.pkg/)**
- * **[OSX binaries](https://haxe.org/download/file/latest/haxe-latest-osx.tar.gz/)**
- * **[Linux Software Packages](https://haxe.org/download/linux/)**
- * **[Linux 32-bit binaries](https://haxe.org/download/file/latest/haxe-latest-linux32.tar.gz/)**
- * **[Linux 64-bit binaries](https://haxe.org/download/file/latest/haxe-latest-linux64.tar.gz/)**
-
-Automated development builds are available from [build.haxe.org](http://build.haxe.org).
-
-## Building from source
-
-See [extra/BUILDING.md](extra/BUILDING.md).
-
-## Using Haxe
-
-For information on using Haxe, consult the [Haxe documentation](https://haxe.org/documentation/):
-
- * [Haxe Introduction](https://haxe.org/documentation/introduction/), an introduction to the Haxe toolkit
- * [The Haxe Manual](https://haxe.org/manual/), the reference manual for the Haxe language
- * [Haxe Code Cookbook](https://code.haxe.org), code snippets / learning resource
- * [Haxe API](https://api.haxe.org), documentation for the Haxe standard and native APIs
- * [Haxelib](https://lib.haxe.org), Haxelib is the package manager for the Haxe Toolkit.
-
-## Community
-
-You can get help and talk with fellow Haxers from around the world via:
-
- * [Haxe Community Forum](http://community.haxe.org)
- * [Haxe on Stack Overflow](https://stackoverflow.com/questions/tagged/haxe)
- * [Haxe Gitter chatroom](https://gitter.im/HaxeFoundation/haxe/)
- * [Haxe Discord server](https://discordapp.com/invite/0uEuWH3spjck73Lo)
-
-:+1: Get notified of the latest Haxe news, don't forget to read the [Haxe roundups](https://haxe.io/).
-
-## Version compatibility
-
-Haxe | Neko | SWF | Python | HL | PHP | Lua |
---------------- | ----- | ----- | ------ | ---- | ---- | --- |
-2.* | 1.* | 8-10 | - | - | - | - |
-3.0.0 | 2.0.0 | | - | - | 5.1+ | - |
-3.2.0 | | 12-14 | 3.2+ | - | | - |
-3.3.0 | 2.1.0 | 21 | | - | | 5.1, 5.2, 5.3, LuaJIT 2.0, 2.1 |
-3.4.0 | | | | 1.1 | 5.4+ and 7.0+ (with `-D php7`) | |
-4.0.0 | 2.3.0 | | | 1.11 | 7.0+ | |
-
-## Contributing
-
-See [CONTRIBUTING.md](CONTRIBUTING.md) for more. Thank you!
+* First PR: https://github.com/HaxeFoundation/haxe/pull/10128
+* Second PR: https://github.com/HaxeFoundation/haxe/pull/11554
+* Third PR: https://github.com/HaxeFoundation/haxe/pull/12168
+
+* Original design repo: https://github.com/nadako/haxe-coroutines/
+* Related Kotlin document: https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md
+* Coroutines under the hood: https://kt.academy/article/cc-under-the-hood
+* Design of Kotlin coroutines: https://www.droidcon.com/2022/09/22/design-of-kotlin-coroutines/
+* Mega document: https://github.com/JetBrains/kotlin/blob/master/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutines-codegen.md
\ No newline at end of file
diff --git a/src-json/meta.json b/src-json/meta.json
index 5c236f1d950..93f3936e195 100644
--- a/src-json/meta.json
+++ b/src-json/meta.json
@@ -142,6 +142,18 @@
"targets": ["TAbstract"],
"links": ["https://haxe.org/manual/types-abstract-core-type.html"]
},
+ {
+ "name": "Coroutine",
+ "metadata": ":coroutine",
+ "doc": "Transform function into a coroutine",
+ "targets": ["TClassField"]
+ },
+ {
+ "name": "CoroutineTransformed",
+ "metadata": ":coroutine.transformed",
+ "doc": "Marks a field as being a coroutine that has already been transformed",
+ "targets": ["TClassField"]
+ },
{
"name": "CppFileCode",
"metadata": ":cppFileCode",
diff --git a/src/codegen/fixOverrides.ml b/src/codegen/fixOverrides.ml
index 12d6514b02e..88bbc2d1c94 100644
--- a/src/codegen/fixOverrides.ml
+++ b/src/codegen/fixOverrides.ml
@@ -41,7 +41,11 @@ let fix_override com c f fd =
let f2 = (try Some (find_field com c f) with Not_found -> None) in
match f2,fd with
| Some (f2), Some(fd) ->
- let targs, tret = (match follow f2.cf_type with TFun (args,ret) -> args, ret | _ -> die "" __LOC__) in
+ let targs, tret =
+ match follow_with_coro f2.cf_type with
+ | Coro (args,ret) -> Common.expand_coro_type com.basic args ret
+ | NotCoro (TFun(args, ret)) -> args, ret
+ | _ -> die "" __LOC__ in
let changed_args = ref [] in
let prefix = "_tmp_" in
let nargs = List.map2 (fun ((v,ct) as cur) (_,_,t2) ->
@@ -86,7 +90,11 @@ let fix_override com c f fd =
f.cf_expr <- Some { fde with eexpr = TFunction fd2 };
f.cf_type <- TFun(targs,tret);
| Some(f2), None when (has_class_flag c CInterface) ->
- let targs, tret = (match follow f2.cf_type with TFun (args,ret) -> args, ret | _ -> die "" __LOC__) in
+ let targs, tret =
+ match follow_with_coro f2.cf_type with
+ | Coro (args,ret) -> Common.expand_coro_type com.basic args ret
+ | NotCoro (TFun(args, ret)) -> args, ret
+ | _ -> die "" __LOC__ in
f.cf_type <- TFun(targs,tret)
| _ ->
()
diff --git a/src/context/common.ml b/src/context/common.ml
index 3eef8c80c8b..30954fd0be9 100644
--- a/src/context/common.ml
+++ b/src/context/common.ml
@@ -754,9 +754,20 @@ let create timer_ctx compilation_step cs version args display_mode =
tfloat = mk_mono();
tbool = mk_mono();
tstring = mk_mono();
+ texception = mk_mono();
tnull = (fun _ -> die "Could use locate abstract Null (was it redefined?)" __LOC__);
tarray = (fun _ -> die "Could not locate class Array (was it redefined?)" __LOC__);
titerator = (fun _ -> die "Could not locate typedef Iterator (was it redefined?)" __LOC__);
+ tunit = mk_mono();
+ tcoro = {
+ tcoro = (fun _ -> die "Could not locate abstract Coroutine (was it redefined?)" __LOC__);
+ continuation = mk_mono();
+ base_continuation_class = null_class;
+ suspension_state = mk_mono();
+ suspension_result = (fun _ -> die "Could not locate class ContinuationResult (was it redefined?)" __LOC__);
+ suspension_result_class = null_class;
+ immediate_suspension_result_class = null_class;
+ }
};
std = null_class;
file_keys = new file_keys;
@@ -887,6 +898,17 @@ let clone com is_macro_context =
tnull = (fun _ -> die "Could use locate abstract Null (was it redefined?)" __LOC__);
tarray = (fun _ -> die "Could not locate class Array (was it redefined?)" __LOC__);
titerator = (fun _ -> die "Could not locate typedef Iterator (was it redefined?)" __LOC__);
+ texception = mk_mono();
+ tunit = mk_mono();
+ tcoro = {
+ tcoro = (fun _ -> die "Could not locate abstract Coroutine (was it redefined?)" __LOC__);
+ continuation = mk_mono();
+ base_continuation_class = null_class;
+ suspension_state = mk_mono();
+ suspension_result = (fun _ -> die "Could not locate class ContinuationResult (was it redefined?)" __LOC__);
+ suspension_result_class = null_class;
+ immediate_suspension_result_class = null_class;
+ };
};
std = null_class;
module_to_file = new hashtbl_lookup;
@@ -1097,6 +1119,11 @@ let get_entry_point com =
(snd path, c, e)
) com.main.main_path
+let expand_coro_type basic args ret =
+ let args = args @ [("_hx_continuation",false,basic.tcoro.continuation)] in
+ let ret = if ExtType.is_void (follow ret) then basic.tunit else ret in
+ (args,basic.tcoro.suspension_result ret)
+
let make_unforced_lazy t_proc f where =
let r = ref (lazy_available t_dynamic) in
r := lazy_wait (fun() ->
diff --git a/src/context/typecore.ml b/src/context/typecore.ml
index 5322386f945..067013a0044 100644
--- a/src/context/typecore.ml
+++ b/src/context/typecore.ml
@@ -92,6 +92,7 @@ type typer_pass_tasks = {
type function_mode =
| FunFunction
+ | FunCoroutine
| FunNotFunction
type typer_globals = {
@@ -113,6 +114,7 @@ type typer_globals = {
mutable delayed_display : DisplayTypes.display_exception_kind option;
root_typer : typer;
(* api *)
+ mutable continuation_api : ContTypes.continuation_api option;
do_macro : typer -> macro_mode -> path -> string -> expr list -> pos -> macro_result;
do_load_macro : typer -> bool -> path -> string -> pos -> ((string * bool * t) list * t * tclass * Type.tclass_field);
do_load_module : ?origin:module_dep_origin -> typer -> path -> pos -> module_def;
@@ -288,8 +290,11 @@ module TyperManager = struct
let e = create_ctx_e ctx.e.curfun FunNotFunction in
create ctx ctx.m ctx.c f e PTypeField ctx.type_params
+ let is_coroutine_context ctx =
+ ctx.e.function_mode = FunCoroutine
+
let is_function_context ctx = match ctx.e.function_mode with
- | FunFunction ->
+ | FunFunction | FunCoroutine ->
true
| FunNotFunction ->
false
diff --git a/src/core/tFunctions.ml b/src/core/tFunctions.ml
index 91893c5e81d..3b874182cbd 100644
--- a/src/core/tFunctions.ml
+++ b/src/core/tFunctions.ml
@@ -649,6 +649,21 @@ let rec follow_lazy_and_mono t = match t with
| _ ->
t
+type maybe_coro =
+ | Coro of tsignature
+ | NotCoro of t
+
+let follow_with_coro t = match follow t with
+ | TAbstract({a_path = (["haxe";"coro"],"Coroutine")},[t]) ->
+ begin match follow t with
+ | TFun(args,ret) ->
+ Coro (args,ret)
+ | t ->
+ NotCoro t
+ end
+ | t ->
+ NotCoro t
+
let rec ambiguate_funs t =
match follow t with
| TFun _ -> TFun ([], t_dynamic)
diff --git a/src/core/tType.ml b/src/core/tType.ml
index c422b85f4b6..79f4aad4330 100644
--- a/src/core/tType.ml
+++ b/src/core/tType.ml
@@ -481,6 +481,18 @@ and build_state =
exception Type_exception of t
+(* TODO: Most of this can probably be moved to the typer context
+ with lazy initialization from the coro code. *)
+type coro_types = {
+ mutable tcoro : (string * bool * t) list -> t -> t;
+ mutable continuation : t;
+ mutable base_continuation_class : tclass;
+ mutable suspension_state : t;
+ mutable suspension_result : t -> t;
+ mutable suspension_result_class : tclass;
+ mutable immediate_suspension_result_class : tclass;
+}
+
type basic_types = {
mutable tvoid : t;
mutable tany : t;
@@ -490,7 +502,10 @@ type basic_types = {
mutable tnull : t -> t;
mutable tstring : t;
mutable tarray : t -> t;
- mutable titerator : t -> t
+ mutable texception : t;
+ mutable titerator : t -> t;
+ mutable tunit : t;
+ mutable tcoro : coro_types;
}
type class_field_scope =
diff --git a/src/core/texpr.ml b/src/core/texpr.ml
index 6447cb1a077..9e8e029f934 100644
--- a/src/core/texpr.ml
+++ b/src/core/texpr.ml
@@ -649,6 +649,24 @@ let for_remap basic v etype e1 e2 p =
mk (TWhile((mk (TParenthesis ehasnext) ehasnext.etype ehasnext.epos),ebody,NormalWhile)) basic.tvoid e1.epos;
]) basic.tvoid p
+let not_while_true_to_while_true basic e1 e2 flag t p =
+ let e_break = mk TBreak t_dynamic p in
+ let e_not = mk (TUnop(Not,Prefix,Builder.mk_parent e1)) e1.etype e1.epos in
+ let e_if eo = mk (TIf(e_not,e_break,eo)) basic.tvoid p in
+ let rec map_continue e = match e.eexpr with
+ | TContinue ->
+ duplicate_tvars e_identity (e_if (Some e))
+ | TWhile _ ->
+ e
+ | _ ->
+ map_expr map_continue e
+ in
+ let e2 = if flag = NormalWhile then e2 else map_continue e2 in
+ let e_if = e_if None in
+ let e_block = if flag = NormalWhile then concat e_if e2 else concat e2 e_if in
+ let e_true = mk (TConst (TBool true)) basic.tbool p in
+ mk (TWhile(e_true,e_block,NormalWhile)) t p
+
(* -------------------------------------------------------------------------- *)
(* BUILD META DATA OBJECT *)
diff --git a/src/coro/contTypes.ml b/src/coro/contTypes.ml
new file mode 100644
index 00000000000..66d20a205ff
--- /dev/null
+++ b/src/coro/contTypes.ml
@@ -0,0 +1,25 @@
+open Type
+
+type continuation_api = {
+ state : tclass_field;
+ result : tclass_field;
+ error : tclass_field;
+ completion : tclass_field;
+ context : tclass_field;
+ goto_label : tclass_field;
+ recursing : tclass_field;
+ immediate_result : texpr -> texpr;
+ immediate_error : texpr -> Type.t -> texpr;
+}
+
+let create_continuation_api immediate_result immediate_error state result error completion context goto_label recursing = {
+ immediate_result;
+ immediate_error;
+ state;
+ result;
+ error;
+ completion;
+ context;
+ goto_label;
+ recursing;
+}
\ No newline at end of file
diff --git a/src/coro/coro.ml b/src/coro/coro.ml
new file mode 100644
index 00000000000..497a41f6184
--- /dev/null
+++ b/src/coro/coro.ml
@@ -0,0 +1,554 @@
+open Globals
+open Type
+open CoroTypes
+open CoroFunctions
+open Texpr
+open ContTypes
+
+let localFuncCount = ref 0
+
+type coro_for =
+ | LocalFunc of tfunc * tvar
+ | ClassField of tclass * tclass_field * tfunc * pos (* expr pos *)
+
+type coro_cls = {
+ params : typed_type_param list;
+ param_types : Type.t list;
+ cls_t : Type.t;
+ result_type : Type.t;
+ cont_type : Type.t;
+}
+
+let substitute_type_params subst t =
+ let rec loop t = match t with
+ | TInst({cl_kind = KTypeParameter ttp}, []) ->
+ (try List.assq ttp subst with Not_found -> t)
+ | _ ->
+ Type.map loop t
+ in
+ loop t
+
+module ContinuationClassBuilder = struct
+ type coro_class = {
+ cls : tclass;
+ name_pos : pos;
+ (* inside = inside the continuation class *)
+ inside : coro_cls;
+ (* outside = in the original function *)
+ outside : coro_cls;
+ type_param_subst : (typed_type_param * Type.t) list;
+ coro_type : coro_for;
+ continuation_api : ContTypes.continuation_api;
+ (* Some coroutine classes (member functions, local functions) need to capture state, this field stores that *)
+ captured : tclass_field option;
+ }
+
+ let create ctx coro_type =
+ let basic = ctx.typer.t in
+ (* Mangle class names to hopefully get unique names and avoid collisions *)
+ let name, cf_captured, result_type, name_pos =
+ let captured_field_name = "captured" in
+ match coro_type with
+ | ClassField (cls, field, tf, _) ->
+ Printf.sprintf "HxCoro_%s_%s_%s" (ctx.typer.m.curmod.m_path |> fst |> String.concat "_") (ctx.typer.m.curmod.m_path |> snd) field.cf_name,
+ (if has_class_field_flag field CfStatic then
+ None
+ else
+ Some (mk_field captured_field_name ctx.typer.c.tthis field.cf_name_pos field.cf_name_pos)),
+ tf.tf_type,
+ field.cf_name_pos
+ | LocalFunc(f,v) ->
+ let n = Printf.sprintf "HxCoroAnonFunc_%i" !localFuncCount in
+ localFuncCount := !localFuncCount + 1;
+
+ let args = List.map (fun (v, _) -> (v.v_name, false, v.v_type)) f.tf_args in
+ let t = TFun (Common.expand_coro_type basic args f.tf_type) in
+
+ n, Some (mk_field captured_field_name t v.v_pos v.v_pos), f.tf_type, v.v_pos
+ in
+
+ let result_type = if ExtType.is_void (follow result_type) then ctx.typer.t.tunit else result_type in
+ (* Is there a pre-existing function somewhere to a valid path? *)
+ let cls_path = ((fst ctx.typer.m.curmod.m_path) @ [ Printf.sprintf "_%s" (snd ctx.typer.m.curmod.m_path) ]), name in
+ let cls = mk_class ctx.typer.m.curmod cls_path name_pos name_pos in
+ let params_outside = ctx.typer.type_params in
+ let params_inside = List.map (fun ttp ->
+ (* TODO: this duplicates clone_type_parameter *)
+ let c = ttp.ttp_class in
+ let map = fun t -> t in (* TODO: ? *)
+ let c = {c with cl_path = ([],ttp.ttp_name)} in
+ let def = Option.map map ttp.ttp_default in
+ let constraints = match ttp.ttp_constraints with
+ | None -> None
+ | Some constraints -> Some (lazy (List.map map (Lazy.force constraints)))
+ in
+ mk_type_param c TPHType (* !!! *) def constraints
+ ) params_outside in
+ cls.cl_params <- params_inside;
+
+ let continuation_api = match ctx.typer.g.continuation_api with
+ | Some api ->
+ api
+ | None ->
+ let cf_state = PMap.find "state" basic.tcoro.suspension_result_class.cl_fields in
+ let cf_result = PMap.find "result" basic.tcoro.suspension_result_class.cl_fields in
+ let cf_error = PMap.find "error" basic.tcoro.suspension_result_class.cl_fields in
+ let cf_completion = PMap.find "completion" basic.tcoro.base_continuation_class.cl_fields in
+ let cf_context = PMap.find "context" basic.tcoro.base_continuation_class.cl_fields in
+ let cf_goto_label = PMap.find "gotoLabel" basic.tcoro.base_continuation_class.cl_fields in
+ let cf_recursing = PMap.find "recursing" basic.tcoro.base_continuation_class.cl_fields in
+ let immediate_result,immediate_error =
+ let c = basic.tcoro.immediate_suspension_result_class in
+ let cf_result = PMap.find "withResult" c.cl_statics in
+ let cf_error = PMap.find "withError" c.cl_statics in
+ (fun e ->
+ CallUnification.make_static_call_better ctx.typer c cf_result [e.etype] [e] (TInst(c,[e.etype])) e.epos
+ ), (fun e t ->
+ CallUnification.make_static_call_better ctx.typer c cf_error [] [e] (TInst(c,[t])) e.epos
+ )
+ in
+ let api = ContTypes.create_continuation_api immediate_result immediate_error cf_state cf_result cf_error cf_completion cf_context cf_goto_label cf_recursing in
+ ctx.typer.g.continuation_api <- Some api;
+ api
+ in
+
+ let param_types_inside = extract_param_types params_inside in
+ let param_types_outside = extract_param_types params_outside in
+ let subst = List.combine params_outside param_types_inside in
+ let result_type_inside = substitute_type_params subst result_type in
+ cls.cl_super <- Some (basic.tcoro.base_continuation_class, [result_type_inside]);
+ cf_captured |> Option.may (fun cf -> cf.cf_type <- substitute_type_params subst cf.cf_type);
+
+ {
+ cls = cls;
+ name_pos;
+ inside = {
+ params = params_inside;
+ param_types = param_types_inside;
+ cls_t = TInst(cls,param_types_inside);
+ result_type = result_type_inside;
+ cont_type = TInst(basic.tcoro.base_continuation_class,[result_type_inside]);
+ };
+ outside = {
+ params = params_outside;
+ param_types = param_types_outside;
+ cls_t = TInst(cls,param_types_outside);
+ result_type = result_type;
+ cont_type = TInst(basic.tcoro.base_continuation_class,[result_type]);
+ };
+ type_param_subst = subst;
+ coro_type = coro_type;
+ continuation_api;
+ captured = cf_captured;
+ }
+
+ let mk_ctor ctx coro_class initial_state =
+ let basic = ctx.typer.t in
+ let b = ctx.builder in
+ let name = "completion" in
+ let ethis = mk (TConst TThis) coro_class.inside.cls_t coro_class.name_pos in
+
+ let vargcompletion = alloc_var VGenerated name basic.tcoro.continuation coro_class.name_pos in
+ let evarargcompletion = b#local vargcompletion coro_class.name_pos in
+ let einitialstate = b#int initial_state coro_class.name_pos in
+ let esuper = b#call (b#super coro_class.inside.cont_type coro_class.name_pos) [ evarargcompletion; einitialstate ] basic.tvoid in
+
+ let this_field cf =
+ b#instance_field ethis coro_class.cls coro_class.inside.param_types cf cf.cf_type
+ in
+
+ let captured =
+ coro_class.captured
+ |> Option.map
+ (fun field ->
+ let vargcaptured = alloc_var VGenerated "captured" field.cf_type coro_class.name_pos in
+ let eargcaptured = b#local vargcaptured coro_class.name_pos in
+ let ecapturedfield = this_field field in
+ vargcaptured, b#assign ecapturedfield eargcaptured)
+ in
+
+ (* If the coroutine field is not static then our HxCoro class needs to capture this for future resuming *)
+
+ let eblock, tfun_args, tfunction_args =
+ let extra_exprs, extra_tfun_args, extra_tfunction_args =
+ captured |>
+ Option.map_default
+ (fun (v, expr) ->
+ [ expr ],
+ [ (v.v_name, false, v.v_type) ],
+ [ (v, None) ])
+ ([], [], [])
+ in
+
+ b#void_block (esuper :: extra_exprs),
+ extra_tfun_args @ [ (name, false, basic.tcoro.continuation) ],
+ extra_tfunction_args @ [ (vargcompletion, None) ]
+ in
+
+ let field = mk_field "new" (TFun (tfun_args, basic.tvoid)) coro_class.name_pos coro_class.name_pos in
+ let func = TFunction { tf_type = basic.tvoid; tf_args = tfunction_args; tf_expr = eblock } in
+ let expr = mk func field.cf_type coro_class.name_pos in
+ field.cf_expr <- Some expr;
+ field.cf_kind <- Method MethNormal;
+
+ if ctx.coro_debug then
+ s_expr_debug expr |> Printf.printf "%s\n";
+
+ field
+
+ let mk_invoke_resume ctx coro_class =
+ let basic = ctx.typer.t in
+ let b = ctx.builder in
+ let tret_invoke_resume = coro_class.inside.cls_t in
+ let ethis = b#this coro_class.inside.cls_t coro_class.name_pos in
+ let ecorocall =
+ let this_field cf =
+ b#instance_field ethis coro_class.cls coro_class.inside.param_types cf cf.cf_type
+ in
+ match coro_class.coro_type with
+ | ClassField (cls, field, f, _) when has_class_field_flag field CfStatic ->
+ let args = (f.tf_args |> List.map (fun (v, _) -> Texpr.Builder.default_value v.v_type coro_class.name_pos)) @ [ ethis ] in
+ let efunction = Builder.make_static_field cls field coro_class.name_pos in
+ b#call efunction args tret_invoke_resume
+ | ClassField (cls, field,f, _) ->
+ let args = (f.tf_args |> List.map (fun (v, _) -> Texpr.Builder.default_value v.v_type coro_class.name_pos)) @ [ ethis ] in
+ let captured = coro_class.captured |> Option.get in
+ let ecapturedfield = this_field captured in
+ let efunction = b#instance_field ecapturedfield cls [] (* TODO: check *) field field.cf_type in
+ b#call efunction args tret_invoke_resume
+ | LocalFunc(f,_) ->
+ let args = (List.map (fun (v, _) -> Texpr.Builder.default_value v.v_type coro_class.name_pos) f.tf_args) @ [ ethis ] in
+ let captured = coro_class.captured |> Option.get in
+ let ecapturedfield = this_field captured in
+ b#call ecapturedfield args tret_invoke_resume
+ in
+ (* TODO: this is awkward, it would be better to avoid the entire expression and work with the correct types right away *)
+ let rec map_expr_type e =
+ Type.map_expr_type map_expr_type (substitute_type_params coro_class.type_param_subst) (fun v -> v) e
+ in
+ let ecorocall = map_expr_type ecorocall in
+
+ let field = mk_field "invokeResume" (TFun ([], tret_invoke_resume)) coro_class.name_pos coro_class.name_pos in
+ add_class_field_flag field CfOverride;
+ let block = b#void_block [ b#return ecorocall ] in
+ let func = TFunction { tf_type = tret_invoke_resume; tf_args = []; tf_expr = block } in
+ let expr = mk (func) basic.tvoid coro_class.name_pos in
+ field.cf_expr <- Some expr;
+ field.cf_kind <- Method MethNormal;
+
+ if ctx.coro_debug then
+ s_expr_debug expr |> Printf.printf "%s\n";
+
+ field
+end
+
+let create_continuation_class ctx coro_class initial_state =
+ let ctor = ContinuationClassBuilder.mk_ctor ctx coro_class initial_state in
+ let resume = ContinuationClassBuilder.mk_invoke_resume ctx coro_class in
+ TClass.add_field coro_class.cls resume;
+ Option.may (TClass.add_field coro_class.cls) coro_class.captured;
+ coro_class.cls.cl_constructor <- Some ctor;
+ if ctx.coro_debug then
+ Printer.s_tclass "\t" coro_class.cls |> Printf.printf "%s\n";
+
+ ctx.typer.m.curmod.m_types <- ctx.typer.m.curmod.m_types @ [ TClassDecl coro_class.cls ]
+
+let coro_to_state_machine ctx coro_class cb_root exprs args vtmp vcompletion vcontinuation stack_item_inserter start_exception =
+ let basic = ctx.typer.t in
+ let b = ctx.builder in
+ let cont = coro_class.ContinuationClassBuilder.continuation_api in
+ let eloop, initial_state, fields = CoroToTexpr.block_to_texpr_coroutine ctx cb_root cont coro_class.cls coro_class.outside.param_types args [ vcompletion.v_id; vcontinuation.v_id ] exprs coro_class.name_pos stack_item_inserter start_exception in
+ (* update cf_type to use inside type parameters *)
+ List.iter (fun cf ->
+ cf.cf_type <- substitute_type_params coro_class.type_param_subst cf.cf_type;
+ TClass.add_field coro_class.cls cf
+ ) fields;
+ create_continuation_class ctx coro_class initial_state;
+ let continuation_var = b#var_init_null vcontinuation in
+
+ let std_is e t =
+ let type_expr = mk (TTypeExpr (module_type_of_type t)) t_dynamic coro_class.name_pos in
+ Texpr.Builder.resolve_and_make_static_call ctx.typer.com.std "isOfType" [e;type_expr] coro_class.name_pos
+ in
+
+ let prefix_arg =
+ match coro_class.coro_type with
+ | ClassField (_, field, _, _) when has_class_field_flag field CfStatic ->
+ []
+ | ClassField _ ->
+ [ b#this ctx.typer.c.tthis coro_class.name_pos ]
+ | LocalFunc(f,v) ->
+ [ b#local v coro_class.name_pos ]
+ in
+
+ let {CoroToTexpr.econtinuation;ecompletion;estate;eresult;egoto;eerror} = exprs in
+
+ let continuation_assign =
+ let t = coro_class.outside.cls_t in
+
+ let ecastedcompletion = mk_cast ecompletion t coro_class.name_pos in
+
+ let tcond =
+ let erecursingfield = b#instance_field ecastedcompletion coro_class.cls coro_class.outside.param_types cont.recursing basic.tbool in
+ let estdis = std_is ecompletion t in
+ let erecursingcheck = b#op_eq erecursingfield (b#bool false coro_class.name_pos) in
+ b#op_bool_and estdis erecursingcheck
+ in
+ let tif = b#assign econtinuation ecastedcompletion in
+ let tif = b#void_block [tif] in
+ let ctor_args = prefix_arg @ [ ecompletion ] in
+ let telse = b#assign econtinuation (mk (TNew (coro_class.cls, coro_class.outside.param_types, ctor_args)) t coro_class.name_pos) in
+ b#if_then_else tcond tif telse basic.tvoid
+ in
+
+ let continuation_field cf t =
+ b#instance_field econtinuation coro_class.cls coro_class.outside.param_types cf t
+ in
+ b#void_block [
+ continuation_var;
+ continuation_assign;
+ b#assign
+ (continuation_field cont.recursing basic.tbool)
+ (b#bool true coro_class.name_pos);
+ b#var_init vtmp eresult;
+ eloop;
+ b#return (b#null basic.tany coro_class.name_pos);
+ ]
+
+let coro_to_normal ctx coro_class cb_root exprs vcontinuation =
+ let open ContinuationClassBuilder in
+ let open CoroToTexpr in
+ let basic = ctx.typer.t in
+ let b = ctx.builder in
+ create_continuation_class ctx coro_class 0;
+ let rec loop cb previous_el =
+ let bad_pos = coro_class.name_pos in
+ let loop cb el =
+ if not (has_block_flag cb CbGenerated) then begin
+ add_block_flag cb CbGenerated;
+ loop cb el
+ end else
+ el,false
+ in
+ let loop_as_block cb =
+ let el,term = loop cb [] in
+ b#void_block el,term
+ in
+ let current_el = ref (previous_el @ (get_block_exprs cb)) in
+ let continue cb_next e =
+ loop cb_next (!current_el @ [e])
+ in
+ let maybe_continue cb_next term e = match cb_next with
+ | Some cb_next when not term ->
+ continue cb_next e
+ | _ ->
+ (!current_el @ [e]),true
+ in
+ let add e = current_el := !current_el @ [e] in
+ let terminate e =
+ add e;
+ !current_el,true
+ in
+ begin match cb.cb_next with
+ | NextSub(cb_sub,cb_next) ->
+ let e_next,term = loop_as_block cb_sub in
+ maybe_continue cb_next term e_next
+ | NextReturn e1 ->
+ let e1 = coro_class.continuation_api.immediate_result e1 in
+ terminate (b#return e1);
+ | NextThrow e1 ->
+ if ctx.throw then
+ terminate (b#throw e1)
+ else begin
+ let e1 = coro_class.continuation_api.immediate_error e1 coro_class.inside.result_type in
+ terminate (b#return e1);
+ end
+ | NextUnknown | NextReturnVoid ->
+ let e1 = coro_class.continuation_api.immediate_result (b#null t_dynamic coro_class.name_pos) in
+ terminate (b#return e1);
+ | NextBreak _ ->
+ terminate (b#break bad_pos);
+ | NextContinue _ ->
+ terminate (b#continue bad_pos);
+ | NextIfThen(e1,cb_then,cb_next) ->
+ let e_then,_ = loop_as_block cb_then in
+ let e_if = b#if_then e1 e_then in
+ continue cb_next e_if
+ | NextIfThenElse(e1,cb_then,cb_else,cb_next) ->
+ let e_then,term_then = loop_as_block cb_then in
+ let e_else,term_else = loop_as_block cb_else in
+ let e_if = b#if_then_else e1 e_then e_else basic.tvoid in
+ maybe_continue cb_next (term_then && term_else) e_if
+ | NextSwitch(switch,cb_next) ->
+ let term = ref true in
+ let p = ref switch.cs_subject.epos in
+ let switch_cases = List.map (fun (el,cb) ->
+ let e,term' = loop_as_block cb in
+ term := !term && term';
+ p := Ast.punion !p e.epos;
+ {
+ case_patterns = el;
+ case_expr = e;
+ }
+ ) switch.cs_cases in
+ let switch_default = Option.map (fun cb ->
+ let e,term' = loop_as_block cb in
+ p := Ast.punion !p e.epos;
+ term := !term && term';
+ e
+ ) switch.cs_default in
+ let switch = {
+ switch_subject = switch.cs_subject;
+ switch_cases;
+ switch_default;
+ switch_exhaustive = switch.cs_exhaustive
+ } in
+ maybe_continue cb_next (switch.switch_exhaustive && !term) (mk (TSwitch switch) basic.tvoid !p)
+ | NextWhile(e1,cb_body,cb_next) ->
+ let e_body,_ = loop_as_block cb_body in
+ let e_while = mk (TWhile(e1,e_body,NormalWhile)) basic.tvoid (Ast.punion e1.epos e_body.epos) in
+ maybe_continue cb_next false e_while
+ | NextTry(cb_try,catches,cb_next) ->
+ let e_try,term = loop_as_block cb_try in
+ let p = ref e_try.epos in
+ let term = ref term in
+ let catches = List.map (fun (v,cb) ->
+ let e,term' = loop_as_block cb in
+ p := Ast.punion !p e.epos;
+ term := !term && term';
+ (v,e)
+ ) catches.cc_catches in
+ let e_try = mk (TTry(e_try,catches)) basic.tvoid !p in
+ maybe_continue cb_next !term e_try
+ | NextFallThrough _ | NextGoto _ ->
+ !current_el,false
+ | NextSuspend(suspend,cb_next) ->
+ let e_sus = CoroToTexpr.make_suspending_call basic suspend {exprs.ecompletion with epos = suspend.cs_pos} in
+ add (mk (TReturn (Some e_sus)) t_dynamic e_sus.epos);
+ !current_el,true
+ end
+ in
+ let el,_ = loop cb_root [] in
+ let e = b#void_block el in
+ let e = if ctx.throw || ctx.nothrow then
+ e
+ else begin
+ let catch =
+ let v = alloc_var VGenerated "e" t_dynamic e.epos in
+ let ev = b#local v e.epos in
+ let eerr = coro_class.continuation_api.immediate_error ev coro_class.inside.result_type in
+ let eret = b#return eerr in
+ (v,eret)
+ in
+ mk (TTry(e,[catch])) basic.tvoid e.epos
+ end in
+ b#void_block [e]
+
+let fun_to_coro ctx coro_type =
+ let basic = ctx.typer.t in
+ let b = ctx.builder in
+
+ let coro_class = ContinuationClassBuilder.create ctx coro_type in
+ let cont = coro_class.continuation_api in
+
+ (* Generate and assign the continuation variable *)
+ let vcompletion = alloc_var VGenerated "_hx_completion" basic.tcoro.continuation coro_class.name_pos in
+ let ecompletion = b#local vcompletion coro_class.name_pos in
+
+ let vcontinuation = alloc_var VGenerated "_hx_continuation" coro_class.outside.cls_t coro_class.name_pos in
+ let econtinuation = b#local vcontinuation coro_class.name_pos in
+
+ let continuation_field cf t =
+ b#instance_field econtinuation basic.tcoro.base_continuation_class coro_class.outside.param_types cf t
+ in
+
+ let egoto = continuation_field cont.goto_label basic.tint in
+ let estate = continuation_field cont.state basic.tcoro.suspension_state in
+ let eresult = continuation_field cont.result basic.tany in
+ let eerror = continuation_field cont.error basic.texception in
+
+ let vtmp = alloc_var VGenerated "_hx_tmp" basic.tany coro_class.name_pos in
+ let etmp = b#local vtmp coro_class.name_pos in
+
+ let expr, args, name =
+ match coro_type with
+ | ClassField (_, cf, f, p) ->
+ f.tf_expr, f.tf_args, cf.cf_name
+ | LocalFunc(f,v) ->
+ f.tf_expr, f.tf_args, v.v_name
+ in
+
+ let cb_root = make_block ctx (Some(expr.etype, coro_class.name_pos)) in
+
+ ignore(CoroFromTexpr.expr_to_coro ctx etmp cb_root expr);
+ let exprs = {CoroToTexpr.econtinuation;ecompletion;estate;eresult;egoto;eerror;etmp} in
+ let stack_item_inserter pos =
+ let field, eargs =
+ match coro_type with
+ | ClassField (cls, field, _, _) ->
+ PMap.find "setClassFuncStackItem" basic.tcoro.base_continuation_class.cl_fields,
+ [
+ b#string (s_class_path cls) coro_class.name_pos;
+ b#string field.cf_name coro_class.name_pos;
+ ]
+ | LocalFunc (f, v) ->
+ PMap.find "setLocalFuncStackItem" basic.tcoro.base_continuation_class.cl_fields,
+ [
+ b#int v.v_id coro_class.name_pos;
+ ]
+ in
+ let eaccess = continuation_field field field.cf_type in
+ let l1,c1,_,_ = Lexer.get_pos_coords pos in
+ let eargs = eargs @ [
+ b#string pos.pfile coro_class.name_pos;
+ b#int l1 coro_class.name_pos;
+ b#int c1 coro_class.name_pos;
+ b#int pos.pmin coro_class.name_pos;
+ b#int pos.pmax coro_class.name_pos;
+ ] in
+ mk (TCall (eaccess, eargs)) basic.tvoid coro_class.name_pos
+ in
+ let start_exception =
+ let cf = PMap.find "startException" basic.tcoro.base_continuation_class.cl_fields in
+ let ef = continuation_field cf cf.cf_type in
+ (fun e ->
+ mk (TCall(ef,[e])) basic.tvoid coro_class.name_pos
+ )
+ in
+ let tf_expr,cb_root = try
+ let cb_root = if ctx.optimize then CoroFromTexpr.optimize_cfg ctx cb_root else cb_root in
+ coro_to_state_machine ctx coro_class cb_root exprs args vtmp vcompletion vcontinuation stack_item_inserter start_exception, cb_root
+ with CoroTco cb_root ->
+ coro_to_normal ctx coro_class cb_root exprs vcontinuation,cb_root
+ in
+
+ let tf_args = args @ [ (vcompletion,None) ] in
+ (* I'm not sure what this should be, but let's stick to the widest one for now.
+ Cpp dies if I try to use coro_class.outside.cls_t here, which might be something
+ to investigate independently. *)
+ let tf_type = basic.tcoro.suspension_result coro_class.outside.result_type in
+ if ctx.coro_debug then begin
+ print_endline ("BEFORE:\n" ^ (s_expr_debug expr));
+ CoroDebug.create_dotgraph (DotGraph.get_dump_path (SafeCom.of_com ctx.typer.com) (ctx.typer.c.curclass.cl_path) name) cb_root
+ end;
+ let e = mk (TFunction {tf_args; tf_expr; tf_type}) (TFun (tf_args |> List.map (fun (v, _) -> (v.v_name, false, v.v_type)), tf_type)) tf_expr.epos in
+ if ctx.coro_debug then print_endline ("AFTER:\n" ^ (s_expr_debug e));
+ e
+
+let create_coro_context typer meta =
+ let optimize = not (Define.raw_defined typer.Typecore.com.defines "coroutine.noopt") in
+ let builder = new CoroElsewhere.texpr_builder typer.t in
+ let ctx = {
+ builder;
+ typer;
+ coro_debug = Meta.has (Meta.Custom ":coroutine.debug") meta;
+ optimize;
+ allow_tco = optimize && not (Meta.has (Meta.Custom ":coroutine.notco") meta);
+ throw = Define.raw_defined typer.com.defines "coroutine.throw";
+ nothrow = Meta.has (Meta.Custom ":coroutine.nothrow") meta;
+ vthis = None;
+ next_block_id = 0;
+ current_catch = None;
+ has_catch = false;
+ } in
+ ctx
diff --git a/src/coro/coroControl.ml b/src/coro/coroControl.ml
new file mode 100644
index 00000000000..fcd58308d31
--- /dev/null
+++ b/src/coro/coroControl.ml
@@ -0,0 +1,33 @@
+open Globals
+open Type
+open Texpr
+
+type coro_control =
+ | CoroPending
+ | CoroReturned
+ | CoroThrown
+
+let mk_int basic i = Texpr.Builder.make_int basic i null_pos
+
+let mk_control basic (c : coro_control) = mk_int basic (Obj.magic c)
+
+let make_custom_control_switch basic e_subject cases p =
+ let cases = List.map (fun (l,e) -> {
+ case_patterns = List.map (mk_control basic) l;
+ case_expr = e;
+ }) cases in
+ let switch = {
+ switch_subject = e_subject;
+ switch_cases = cases;
+ switch_default = None;
+ switch_exhaustive = true;
+ } in
+ mk (TSwitch switch) basic.tvoid p
+
+let make_control_switch basic e_subject e_pending e_returned e_thrown p =
+ let cases = [
+ [CoroPending],e_pending;
+ [CoroReturned],e_returned;
+ [CoroThrown],e_thrown;
+ ] in
+ make_custom_control_switch basic e_subject cases p
\ No newline at end of file
diff --git a/src/coro/coroDebug.ml b/src/coro/coroDebug.ml
new file mode 100644
index 00000000000..12ff8bc1f3a
--- /dev/null
+++ b/src/coro/coroDebug.ml
@@ -0,0 +1,94 @@
+
+open CoroTypes
+open CoroFunctions
+open Type
+
+let create_dotgraph path cb =
+ print_endline (String.concat "." path);
+ let ch,close = DotGraph.start_graph path ".coro" in
+ let pctx = print_context() in
+ let st = s_type pctx in
+ let se = s_expr_pretty true "" false st in
+ let edges = DynArray.create () in
+ let rec block cb =
+ let edge_block label cb_target =
+ block cb_target;
+ DynArray.add edges (cb.cb_id,cb_target.cb_id,label,true);
+ in
+ let maybe_edge_block label = Option.may (edge_block label) in
+ let s = String.concat "\n" (DynArray.to_list (DynArray.map se cb.cb_el)) in
+ let flags = if has_block_flag cb CbResumeState then " resume" else if has_block_flag cb CbSuspendState then " suspend" else "" in
+ let s = if s = "" then Printf.sprintf "(%i%s)" cb.cb_id flags else Printf.sprintf "(%i%s)\n%s" cb.cb_id flags s in
+ let snext = match cb.cb_next with
+ | NextUnknown ->
+ None
+ | NextSub(cb_sub,cb_next) ->
+ maybe_edge_block "next" cb_next;
+ edge_block "sub" cb_sub;
+ None
+ | NextBreak cb_break ->
+ DynArray.add edges (cb.cb_id,cb_break.cb_id,"goto",false);
+ Some "break"
+ | NextContinue cb_continue ->
+ DynArray.add edges (cb.cb_id,cb_continue.cb_id,"goto",false);
+ Some "continue"
+ | NextReturnVoid ->
+ Some "return"
+ | NextReturn e ->
+ Some ("return " ^ se e)
+ | NextThrow e ->
+ Some ("throw " ^ se e)
+ | NextIfThen(e,cb_then,cb_next) ->
+ edge_block "next" cb_next;
+ edge_block "then" cb_then;
+ Some ("if " ^ se e)
+ | NextIfThenElse(e,cb_then,cb_else,cb_next) ->
+ maybe_edge_block "next" cb_next;
+ edge_block "then" cb_then;
+ edge_block "else" cb_else;
+ Some ("if " ^ se e)
+ | NextSwitch(switch,cb_next) ->
+ maybe_edge_block "next" cb_next;
+ List.iter (fun (el,cb_case) ->
+ edge_block (String.concat " | " (List.map se el)) cb_case
+ ) switch.cs_cases;
+ Option.may (fun cb_default -> edge_block "default" cb_default) switch.cs_default;
+ Some ("switch " ^ se switch.cs_subject)
+ | NextWhile(e,cb_body,cb_next) ->
+ maybe_edge_block "next" cb_next;
+ edge_block "body" cb_body;
+ Some ("while " ^ se e)
+ | NextTry(cb_try,catch,cb_next) ->
+ maybe_edge_block "next" cb_next;
+ edge_block "try" cb_try;
+ DynArray.add edges (cb_try.cb_id,catch.cc_cb.cb_id,"catch",true);
+ Printf.fprintf ch "n%i [shape=box,label=\"(%i)\"];\n" catch.cc_cb.cb_id catch.cc_cb.cb_id;
+ List.iter (fun (v,cb_catch) ->
+ block cb_catch;
+ DynArray.add edges (catch.cc_cb.cb_id,cb_catch.cb_id,(st v.v_type),true);
+ ) catch.cc_catches;
+ None
+ | NextSuspend(suspend,cb_next) ->
+ maybe_edge_block "next" cb_next;
+ Some (Printf.sprintf "%s(%s)" (se suspend.cs_fun) (String.concat ", " (List.map se suspend.cs_args)))
+ | NextFallThrough cb_next ->
+ DynArray.add edges (cb.cb_id,cb_next.cb_id,"fall-through",false);
+ None
+ | NextGoto cb_next ->
+ DynArray.add edges (cb.cb_id,cb_next.cb_id,"goto",false);
+ None
+ in
+ let s = match snext with
+ | None ->
+ s
+ | Some snext ->
+ if s = "" then snext else s ^ "\n" ^ snext
+ in
+ Printf.fprintf ch "n%i [shape=box,label=\"%s\"];\n" cb.cb_id (StringHelper.s_escape s);
+ in
+ ignore(block cb);
+ DynArray.iter (fun (id_from,id_to,label,tree_edge) ->
+ let style = if tree_edge then "style=\"solid\",color=\"black\"" else "style=\"dashed\", color=\"lightgray\"" in
+ Printf.fprintf ch "n%i -> n%i[%s label=\"%s\"];\n" id_from id_to style (StringHelper.s_escape label);
+ ) edges;
+ close();
\ No newline at end of file
diff --git a/src/coro/coroElsewhere.ml b/src/coro/coroElsewhere.ml
new file mode 100644
index 00000000000..0e71eec3f60
--- /dev/null
+++ b/src/coro/coroElsewhere.ml
@@ -0,0 +1,77 @@
+(*
+ Code that should eventually be moved elsewhere.
+*)
+
+open Globals
+open Ast
+open Type
+
+class texpr_builder (basic : basic_types) =
+object(self)
+ method assign (lhs : texpr) (rhs : texpr) =
+ mk (TBinop(OpAssign,lhs,rhs)) lhs.etype (punion lhs.epos rhs.epos)
+
+ method binop (op : binop) (lhs : texpr) (rhs : texpr) (t : Type.t) =
+ mk (TBinop(op,lhs,rhs)) t (punion lhs.epos rhs.epos)
+
+ method bool (b : bool) (p : pos) =
+ mk (TConst (TBool b)) basic.tbool p
+
+ method break (p : pos) =
+ mk TBreak t_dynamic p
+
+ method call (e1 : texpr) (el : texpr list) (tret : Type.t) =
+ mk (TCall(e1,el)) tret (punion e1.epos (punion_el e1.epos el))
+
+ method continue (p : pos) =
+ mk TContinue t_dynamic p
+
+ method local (v : tvar) (p : pos) =
+ mk (TLocal v) v.v_type p
+
+ method if_then (eif : texpr) (ethen : texpr) =
+ mk (TIf(eif,ethen,None)) basic.tvoid (punion eif.epos ethen.epos)
+
+ method if_then_else (eif : texpr) (ethen : texpr) (eelse : texpr) (t : Type.t) =
+ mk (TIf(eif,ethen,Some eelse)) t (punion eif.epos eelse.epos)
+
+ method instance_field (e : texpr) (c : tclass) (params : Type.t list) (cf : tclass_field) (t : Type.t) =
+ mk (TField(e,FInstance(c,params,cf))) t e.epos
+
+ method int (i : int) (p : pos) =
+ mk (TConst (TInt (Int32.of_int i))) basic.tint p
+
+ method null (t : Type.t) (p : pos) =
+ mk (TConst TNull) t p
+
+ method op_bool_and (e1 : texpr) (e2 : texpr) =
+ self#binop OpBoolAnd e1 e2 basic.tbool
+
+ method op_eq (e1 : texpr) (e2 : texpr) =
+ self#binop OpEq e1 e2 basic.tbool
+
+ method return (e : texpr) =
+ mk (TReturn (Some e)) t_dynamic e.epos
+
+ method string (s : string) (p : pos) =
+ mk (TConst (TString s)) basic.tstring p
+
+ method super (t: Type.t) (p : pos) =
+ mk (TConst TSuper) t p
+
+ method this (t : Type.t) (p : pos) =
+ mk (TConst TThis) t p
+
+ method throw (e : texpr) =
+ mk (TThrow e) t_dynamic e.epos
+
+ method var_init (v : tvar) (e : texpr) =
+ mk (TVar(v,Some e)) basic.tvoid (punion v.v_pos e.epos)
+
+ method var_init_null (v : tvar) =
+ self#var_init v (self#null v.v_type v.v_pos)
+
+ method void_block (el : texpr list) =
+ mk (TBlock el) basic.tvoid (Texpr.punion_el null_pos el)
+
+end
\ No newline at end of file
diff --git a/src/coro/coroFromTexpr.ml b/src/coro/coroFromTexpr.ml
new file mode 100644
index 00000000000..f3b14297627
--- /dev/null
+++ b/src/coro/coroFromTexpr.ml
@@ -0,0 +1,486 @@
+open Globals
+open Type
+open CoroTypes
+open CoroFunctions
+
+let e_no_value = Texpr.Builder.make_null t_dynamic null_pos
+
+type coro_ret =
+ | RLocal of tvar
+ | RTerminate of (coro_block -> texpr -> unit)
+ | RValue
+ | RBlock
+
+let expr_to_coro ctx etmp cb_root e =
+ let ordered_value_marker = ref false in
+ let start_ordered_value_list () =
+ let old = !ordered_value_marker in
+ (fun () ->
+ let cur = !ordered_value_marker in
+ ordered_value_marker := old;
+ cur
+ )
+ in
+ let make_block typepos =
+ make_block ctx typepos
+ in
+ let block_from_e e =
+ make_block (Some(e.etype,e.epos))
+ in
+ let has_side_effect e = match e.eexpr with
+ | TVar _ ->
+ (* has_side_effect doesn't consider var declarations a side effect which may just be wrong *)
+ true
+ | _ ->
+ OptimizerTexpr.has_side_effect e
+ in
+ let add_expr cb e =
+ if cb.cb_next = NextUnknown && e != e_no_value && has_side_effect e then
+ DynArray.add cb.cb_el e
+ in
+ let terminate cb kind t p =
+ if cb.cb_next = NextUnknown then
+ cb.cb_next <- kind;
+ in
+ let fall_through cb_from cb_to =
+ terminate cb_from (NextFallThrough cb_to) t_dynamic null_pos
+ in
+ let goto cb_from cb_to =
+ terminate cb_from (NextGoto cb_to) t_dynamic null_pos
+ in
+ let loop_stack = ref [] in
+ let rec loop cb ret e = match e.eexpr with
+ (* special cases *)
+ | TConst TThis ->
+ Some (cb,e)
+ (* simple values *)
+ | TConst _ | TLocal _ | TTypeExpr _ | TIdent _ ->
+ Some (cb,e)
+ (* compound values *)
+ | TBlock [e1] ->
+ loop cb ret e1
+ | TBlock el ->
+ let cb_sub = block_from_e e in
+ let ret = match ret,el with
+ | RValue,_ :: _ ->
+ (*
+ If we have a multi-element block in a value-place we might need a temp var
+ because the result expression might reference local variables declared in
+ that block (https://github.com/Aidan63/haxe/issues/79).
+ *)
+ let v = alloc_var VGenerated "tmp" e.etype e.epos in
+ add_expr cb {e with eexpr = TVar(v,None)};
+ RLocal v
+ | _ ->
+ ret
+ in
+ let sub_next = loop_block cb_sub ret e in
+ let cb_next = match sub_next with
+ | None ->
+ None
+ | Some (cb_sub_next,e1) ->
+ let cb_next = make_block None in
+ fall_through cb_sub_next cb_next;
+ Some (cb_next,e1)
+ in
+ terminate cb (NextSub(cb_sub,Option.map fst cb_next)) e.etype e.epos;
+ cb_next
+ | TArray(e1,e2) ->
+ let cb = ordered_loop cb [e1;e2] in
+ Option.map (fun (cb,el) -> match el with
+ | [e1;e2] ->
+ (cb,{e with eexpr = TArray(e1,e2)})
+ | _ ->
+ die "" __LOC__
+ ) cb
+ | TArrayDecl el ->
+ let cb = ordered_loop cb el in
+ Option.map (fun (cb,el) -> (cb,{e with eexpr = TArrayDecl el})) cb
+ | TObjectDecl fl ->
+ let cb = ordered_loop cb (List.map snd fl) in
+ Option.map (fun (cb,el) ->
+ let fl = List.map2 (fun (f,_) e -> (f,e)) fl el in
+ (cb,{e with eexpr = TObjectDecl fl})
+ ) cb
+ | TField(e1,fa) ->
+ (* TODO: this is quite annoying because factoring out field access behaves very creatively on
+ some targets. This means that (coroCall()).field doesn't work (and isn't tested). *)
+ Some (cb,e)
+ | TEnumParameter(e1,ef,i) ->
+ let cb = loop cb RValue e1 in
+ Option.map (fun (cb,e) -> (cb,{e with eexpr = TEnumParameter(e1,ef,i)})) cb
+ | TEnumIndex e1 ->
+ let cb = loop cb RValue e1 in
+ Option.map (fun (cb,e1) -> (cb,{e with eexpr = TEnumIndex e1})) cb
+ | TNew(c,tl,el) ->
+ let cb = ordered_loop cb el in
+ Option.map (fun (cb,e1) -> cb,{e with eexpr = TNew(c,tl,el)}) cb
+ (* rewrites & forwards *)
+ | TCast(e1,o) ->
+ let cb = loop cb ret e1 in
+ Option.map (fun (cb,e1) -> (cb,{e with eexpr = TCast(e1,o)})) cb
+ | TParenthesis e1 ->
+ let cb = loop cb ret e1 in
+ Option.map (fun (cb,e1) -> (cb,{e with eexpr = TParenthesis e1})) cb
+ | TMeta(meta,e1) ->
+ let cb = loop cb ret e1 in
+ Option.map (fun (cb,e1) -> (cb,{e with eexpr = TMeta(meta,e1)})) cb
+ | TUnop(op,flag,e1) ->
+ let cb = loop cb ret (* TODO: is this right? *) e1 in
+ Option.map (fun (cb,e1) -> (cb,{e with eexpr = TUnop(op,flag,e1)})) cb
+ | TBinop(OpAssign,({eexpr = TLocal v} as e1),e2) ->
+ let cb = loop_assign cb (RLocal v) e2 in
+ Option.map (fun (cb,e2) -> (cb,{e with eexpr = TBinop(OpAssign,e1,e2)})) cb
+ (* TODO: OpAssignOp and other OpAssign *)
+ | TBinop(op,e1,e2) ->
+ let cb = loop cb RValue e1 in
+ begin match cb with
+ | None ->
+ None
+ | Some (cb,e1) ->
+ let cb2 = loop cb RValue e2 in
+ begin match cb2 with
+ | None ->
+ add_expr cb e1;
+ None
+ | Some (cb,e2) ->
+ Some (cb,{e with eexpr = TBinop(op,e1,e2)})
+ end
+ end
+ (* variables *)
+ | TVar(v,None) ->
+ add_expr cb e;
+ Some (cb,e_no_value)
+ | TVar(v,Some e1) ->
+ add_expr cb {e with eexpr = TVar(v,None)};
+ let cb = loop_assign cb (RLocal v) e1 in
+ cb
+ (* calls *)
+ | TCall(e1,el) ->
+ let cb = ordered_loop cb (e1 :: el) in
+ Option.map (fun (cb,el) ->
+ begin match el with
+ | e1 :: el ->
+ begin match follow_with_coro e1.etype with
+ | Coro _ ->
+ let cb_next = block_from_e e1 in
+ add_block_flag cb_next CbResumeState;
+ let suspend = {
+ cs_fun = e1;
+ cs_args = el;
+ cs_pos = e.epos
+ } in
+ add_block_flag cb CbSuspendState;
+ terminate cb (NextSuspend(suspend,Some cb_next)) t_dynamic null_pos;
+ cb_next,etmp
+ | _ ->
+ cb,{e with eexpr = TCall(e1,el)}
+ end
+ | [] ->
+ die "" __LOC__
+ end
+ ) cb
+ (* terminators *)
+ | TBreak ->
+ terminate cb (NextBreak (Lazy.force (snd (List.hd !loop_stack)))) e.etype e.epos;
+ None
+ | TContinue ->
+ terminate cb (NextContinue (fst (List.hd !loop_stack))) e.etype e.epos;
+ None
+ | TReturn None ->
+ terminate cb NextReturnVoid e.etype e.epos;
+ None
+ | TReturn (Some e1) ->
+ let f_terminate cb e1 =
+ terminate cb (NextReturn e1) e.etype e.epos;
+ in
+ let ret = RTerminate f_terminate in
+ let cb_ret = loop_assign cb ret e1 in
+ Option.may (fun (cb_ret,e1) -> terminate cb_ret (NextReturn e1) e.etype e.epos) cb_ret;
+ None
+ | TThrow e1 ->
+ let f_terminate cb e1 =
+ terminate cb (NextThrow e1) e.etype e.epos;
+ in
+ let ret = RTerminate f_terminate in
+ let cb_ret = loop_assign cb ret e1 in
+ Option.may (fun (cb_ret,e1) -> terminate cb_ret (NextThrow e1) e.etype e.epos) cb_ret;
+ None
+ (* branching *)
+ | TIf(e1,e2,None) ->
+ let cb = loop cb RValue e1 in
+ Option.map (fun (cb,e1) ->
+ let cb_then = block_from_e e2 in
+ let cb_then_next = loop_block cb_then RBlock e2 in
+ let cb_next = make_block None in
+ Option.may (fun (cb_then_next,_) -> fall_through cb_then_next cb_next) cb_then_next;
+ terminate cb (NextIfThen(e1,cb_then,cb_next)) e.etype e.epos;
+ cb_next,e_no_value
+ ) cb
+ | TIf(e1,e2,Some e3) ->
+ let cb = loop cb RValue e1 in
+ begin match cb with
+ | None ->
+ None
+ | Some(cb,e1) ->
+ let cb_then = block_from_e e2 in
+ let cb_then_next = loop_block cb_then ret e2 in
+ let cb_else = block_from_e e3 in
+ let cb_else_next = loop_block cb_else ret e3 in
+ let cb_next = match cb_then_next,cb_else_next with
+ | Some (cb_then_next,_),Some(cb_else_next,_) ->
+ let cb_next = make_block None in
+ fall_through cb_then_next cb_next;
+ fall_through cb_else_next cb_next;
+ Some cb_next
+ | (Some (cb_branch_next,_),None) | (None,Some (cb_branch_next,_)) ->
+ let cb_next = make_block None in
+ fall_through cb_branch_next cb_next;
+ Some cb_next
+ | None,None ->
+ None
+ in
+ terminate cb (NextIfThenElse(e1,cb_then,cb_else,cb_next)) e.etype e.epos;
+ Option.map (fun cb_next -> (cb_next,e_no_value)) cb_next
+ end
+ | TSwitch switch ->
+ let e1 = switch.switch_subject in
+ let cb = loop cb RValue e1 in
+ begin match cb with
+ | None ->
+ None
+ | Some(cb,e1) ->
+ let cb_next = lazy (make_block None) in
+ let cases = List.map (fun case ->
+ let cb_case = block_from_e case.case_expr in
+ let cb_case_next = loop_block cb_case ret case.case_expr in
+ Option.may (fun (cb_case_next,_) ->
+ fall_through cb_case_next (Lazy.force cb_next);
+ ) cb_case_next;
+ (case.case_patterns,cb_case)
+ ) switch.switch_cases in
+ let def = match switch.switch_default with
+ | None ->
+ None
+ | Some e ->
+ let cb_default = block_from_e e in
+ let cb_default_next = loop_block cb_default ret e in
+ Option.may (fun (cb_default_next,_) ->
+ fall_through cb_default_next (Lazy.force cb_next);
+ ) cb_default_next;
+ Some cb_default
+ in
+ let switch = {
+ cs_subject = e1;
+ cs_cases = cases;
+ cs_default = def;
+ cs_exhaustive = switch.switch_exhaustive
+ } in
+ let cb_next = if Lazy.is_val cb_next || not switch.cs_exhaustive then Some (Lazy.force cb_next) else None in
+ terminate cb (NextSwitch(switch,cb_next)) e.etype e.epos;
+ Option.map (fun cb_next -> (cb_next,e_no_value)) cb_next
+ end
+ | TWhile(e1,e2,flag) when not (is_true_expr e1) ->
+ loop cb ret (Texpr.not_while_true_to_while_true ctx.typer.com.Common.basic e1 e2 flag e.etype e.epos)
+ | TWhile(e1,e2,flag) (* always while(true) *) ->
+ let cb_next = lazy (make_block None) in
+ let cb_body = block_from_e e2 in
+ loop_stack := (cb_body,cb_next) :: !loop_stack;
+ let cb_body_next = loop_block cb_body RBlock e2 in
+ Option.may (fun (cb_body_next,_) -> goto cb_body_next cb_body) cb_body_next;
+ loop_stack := List.tl !loop_stack;
+ let cb_next = if Lazy.is_val cb_next then Some (Lazy.force cb_next) else None in
+ terminate cb (NextWhile(e1,cb_body,cb_next)) e.etype e.epos;
+ Option.map (fun cb_next -> (cb_next,e_no_value)) cb_next
+ | TTry(e1,catches) ->
+ ctx.has_catch <- true;
+ let cb_next = lazy (make_block None) in
+ let catches = List.map (fun (v,e) ->
+ let cb_catch = block_from_e e in
+ add_expr cb_catch (mk (TVar(v,Some etmp)) ctx.typer.t.tvoid null_pos);
+ let cb_catch_next = loop_block cb_catch ret e in
+ Option.may (fun (cb_catch_next,_) ->
+ fall_through cb_catch_next (Lazy.force cb_next);
+ ) cb_catch_next;
+ v,cb_catch
+ ) catches in
+ let catch = make_block None in
+ (* This block is handled in a special way in the texpr transformer, let's mark it as
+ already generated so we don't generate it twice. *)
+ add_block_flag catch CbGenerated;
+ let old = ctx.current_catch in
+ ctx.current_catch <- Some catch;
+ let catch = {
+ cc_cb = catch;
+ cc_catches = catches;
+ } in
+ let cb_try = block_from_e e1 in
+ let cb_try_next = loop_block cb_try ret e1 in
+ ctx.current_catch <- old;
+ Option.may (fun (cb_try_next,_) ->
+ fall_through cb_try_next (Lazy.force cb_next)
+ ) cb_try_next;
+ let cb_next = if Lazy.is_val cb_next then Some (Lazy.force cb_next) else None in
+ terminate cb (NextTry(cb_try,catch,cb_next)) e.etype e.epos;
+ Option.map (fun cb_next -> (cb_next,e_no_value)) cb_next
+ | TFunction tf ->
+ Some (cb,e)
+ and ordered_loop cb el =
+ let close = start_ordered_value_list () in
+ let rec aux' cb acc el = match el with
+ | [] ->
+ Some (cb,List.rev acc)
+ | e :: el ->
+ let cb' = loop cb RValue e in
+ match cb' with
+ | None ->
+ List.iter (fun e ->
+ add_expr cb e
+ ) (List.rev acc);
+ None
+ | Some (cb,e) ->
+ aux' cb (e :: acc) el
+ in
+ let cb = aux' cb [] el in
+ let _ = close () in
+ cb
+ and loop_assign cb ret e =
+ let cb = loop cb ret e in
+ match cb with
+ | Some (cb,e) when e != e_no_value ->
+ begin match ret with
+ | RBlock ->
+ add_expr cb e;
+ Some (cb,e_no_value)
+ | RValue ->
+ Some (cb,e)
+ | RLocal v ->
+ let ev = Texpr.Builder.make_local v v.v_pos in
+ let eass = Texpr.Builder.binop OpAssign ev e ev.etype ev.epos in
+ add_expr cb eass;
+ Some (cb,ev)
+ | RTerminate f ->
+ f cb e;
+ None
+ end
+ | Some(cb,e) ->
+ Some(cb,e)
+ | None ->
+ None
+ and loop_block cb ret e =
+ let el = match e.eexpr with
+ | TBlock el ->
+ el
+ | _ ->
+ [e]
+ in
+ let rec aux' cb el = match el with
+ | [] ->
+ assert false
+ | [e] ->
+ loop_assign cb ret e
+ | e :: el ->
+ let cb = loop cb RBlock e in
+ begin match cb with
+ | None ->
+ None
+ | Some(cb,e) ->
+ add_expr cb e;
+ aux' cb el
+ end
+ in
+ match el with
+ | [] ->
+ None
+ | _ ->
+ aux' cb el
+ in
+ loop_block cb_root RBlock e
+
+let optimize_cfg ctx cb =
+ let forward_el cb_from cb_to =
+ if DynArray.length cb_from.cb_el > 0 then begin
+ if DynArray.length cb_to.cb_el = 0 then begin
+ DynArray.iter (fun e -> DynArray.add cb_to.cb_el e) cb_from.cb_el
+ end else begin
+ let e = mk (TBlock (DynArray.to_list cb_from.cb_el)) ctx.typer.t.tvoid null_pos in
+ DynArray.set cb_to.cb_el 0 (concat e (DynArray.get cb_to.cb_el 0))
+ end
+ end
+ in
+ (* first pass: find empty blocks and store their replacement*)
+ let forward = Array.make ctx.next_block_id None in
+ let rec loop cb =
+ if not (has_block_flag cb CbEmptyMarked) then begin
+ add_block_flag cb CbEmptyMarked;
+ match cb.cb_next with
+ | NextSub(cb_sub,None) ->
+ loop cb_sub;
+ forward_el cb cb_sub;
+ if has_block_flag cb CbResumeState then add_block_flag cb_sub CbResumeState;
+ forward.(cb.cb_id) <- Some cb_sub
+ | NextFallThrough cb_next | NextGoto cb_next when DynArray.empty cb.cb_el && not (has_block_flag cb CbResumeState) ->
+ loop cb_next;
+ forward.(cb.cb_id) <- Some cb_next
+ | _ ->
+ coro_iter loop cb
+ end
+ in
+ loop cb;
+ (* second pass: map graph to skip forwarding block *)
+ let rec loop cb = match forward.(cb.cb_id) with
+ | Some cb ->
+ loop cb
+ | None ->
+ if not (has_block_flag cb CbForwardMarked) then begin
+ add_block_flag cb CbForwardMarked;
+ coro_next_map loop cb;
+ end;
+ cb
+ in
+ let cb = loop cb in
+ let is_empty_termination_block cb = match cb with
+ | None ->
+ true
+ | Some cb ->
+ DynArray.empty cb.cb_el && match cb.cb_next with
+ | NextReturnVoid | NextUnknown ->
+ true
+ | _ ->
+ false
+ in
+ let rec loop cb =
+ if not (has_block_flag cb CbTcoChecked) then begin
+ add_block_flag cb CbTcoChecked;
+ begin match cb.cb_next with
+ | NextSuspend(_,cb_next) ->
+ if not (is_empty_termination_block cb_next) then
+ raise Exit;
+ | _ ->
+ ()
+ end;
+ coro_iter loop cb;
+ end
+ in
+ if ctx.allow_tco && not ctx.has_catch then
+ (try loop cb; raise (CoroTco cb) with Exit -> ());
+ (* third pass: reindex cb_id for tighter switches. Breadth-first because that makes the numbering more natural, maybe. *)
+ let i = ref 0 in
+ let queue = Queue.create () in
+ Queue.push cb queue;
+ let rec loop () =
+ if not (Queue.is_empty queue) then begin
+ let cb = Queue.pop queue in
+ if not (has_block_flag cb CbReindexed) then begin
+ add_block_flag cb CbReindexed;
+ cb.cb_id <- !i;
+ incr i;
+ coro_iter (fun cb -> Queue.add cb queue) cb;
+ Option.may (fun cb -> Queue.add cb queue) cb.cb_catch;
+ end;
+ loop ()
+ end
+ in
+ loop ();
+ ctx.next_block_id <- !i;
+ cb
\ No newline at end of file
diff --git a/src/coro/coroFunctions.ml b/src/coro/coroFunctions.ml
new file mode 100644
index 00000000000..f84c942cf0d
--- /dev/null
+++ b/src/coro/coroFunctions.ml
@@ -0,0 +1,122 @@
+open Globals
+open Type
+open CoroTypes
+
+let make_block ctx typepos =
+ let id = ctx.next_block_id in
+ ctx.next_block_id <- ctx.next_block_id + 1;
+ {
+ cb_id = id;
+ cb_el = DynArray.create ();
+ cb_typepos = typepos;
+ cb_next = NextUnknown;
+ cb_catch = ctx.current_catch;
+ cb_flags = 0;
+ }
+
+let add_block_flag cb (flag : cb_flag) =
+ cb.cb_flags <- set_flag cb.cb_flags (Obj.magic flag)
+
+let has_block_flag cb (flag : cb_flag) =
+ has_flag cb.cb_flags (Obj.magic flag)
+
+let get_block_exprs cb =
+ let rec loop idx acc =
+ if idx < 0 then
+ acc
+ else begin
+ let acc = match DynArray.unsafe_get cb.cb_el idx with
+ | {eexpr = TBlock el} ->
+ el @ acc
+ | e ->
+ e :: acc
+ in
+ loop (idx - 1) acc
+ end in
+ loop (DynArray.length cb.cb_el - 1) []
+
+let coro_iter f cb =
+ let fo = Option.may f in
+ fo cb.cb_catch;
+ match cb.cb_next with
+ | NextSub(cb_sub,cb_next) ->
+ f cb_sub;
+ fo cb_next
+ | NextIfThen(_,cb_then,cb_next) ->
+ f cb_then;
+ f cb_next;
+ | NextIfThenElse(_,cb_then,cb_else,cb_next) ->
+ f cb_then;
+ f cb_else;
+ fo cb_next;
+ | NextSwitch(switch,cb_next) ->
+ List.iter (fun (_,cb) -> f cb) switch.cs_cases;
+ Option.may f switch.cs_default;
+ fo cb_next;
+ | NextWhile(e,cb_body,cb_next) ->
+ f cb_body;
+ fo cb_next;
+ | NextTry(cb_try,catch,cb_next) ->
+ f cb_try;
+ f catch.cc_cb;
+ List.iter (fun (_,cb) -> f cb) catch.cc_catches;
+ fo cb_next;
+ | NextSuspend(call,cb_next) ->
+ fo cb_next
+ | NextBreak cb_next | NextContinue cb_next | NextFallThrough cb_next | NextGoto cb_next ->
+ f cb_next;
+ | NextUnknown | NextReturnVoid | NextReturn _ | NextThrow _ ->
+ ()
+
+let coro_next_map f cb =
+ Option.may (fun cb_catch -> cb.cb_catch <- Some (f cb_catch)) cb.cb_catch;
+ let fo = Option.map f in
+ match cb.cb_next with
+ | NextSub(cb_sub,cb_next) ->
+ let cb_sub = f cb_sub in
+ let cb_next = fo cb_next in
+ cb.cb_next <- NextSub(cb_sub,cb_next);
+ | NextIfThen(e,cb_then,cb_next) ->
+ let cb_then = f cb_then in
+ let cb_next = f cb_next in
+ cb.cb_next <- NextIfThen(e,cb_then,cb_next);
+ | NextIfThenElse(e,cb_then,cb_else,cb_next) ->
+ let cb_then = f cb_then in
+ let cb_else = f cb_else in
+ let cb_next = fo cb_next in
+ cb.cb_next <- NextIfThenElse(e,cb_then,cb_else,cb_next);
+ | NextSwitch(switch,cb_next) ->
+ let cases = List.map (fun (el,cb) -> (el,f cb)) switch.cs_cases in
+ let def = Option.map f switch.cs_default in
+ let switch = {
+ switch with cs_cases = cases; cs_default = def
+ } in
+ let cb_next = fo cb_next in
+ cb.cb_next <- NextSwitch(switch,cb_next);
+ | NextWhile(e,cb_body,cb_next) ->
+ let cb_body = f cb_body in
+ let cb_next = fo cb_next in
+ cb.cb_next <- NextWhile(e,cb_body,cb_next);
+ | NextTry(cb_try,catch,cb_next) ->
+ let cb_try = f cb_try in
+ let cc_cb = f catch.cc_cb in
+ let catches = List.map (fun (v,cb) -> (v,f cb)) catch.cc_catches in
+ let catch = {
+ cc_cb;
+ cc_catches = catches
+ } in
+ let cb_next = fo cb_next in
+ cb.cb_next <- NextTry(cb_try,catch,cb_next);
+ | NextSuspend(call,cb_next) ->
+ let cb_next = fo cb_next in
+ cb.cb_next <- NextSuspend(call,cb_next);
+ | NextBreak cb_next ->
+ cb.cb_next <- NextBreak (f cb_next);
+ | NextContinue cb_next ->
+ cb.cb_next <- NextContinue (f cb_next);
+ | NextGoto cb_next ->
+ cb.cb_next <- NextGoto (f cb_next);
+ | NextFallThrough cb_next ->
+ cb.cb_next <- NextFallThrough (f cb_next);
+ | NextReturnVoid | NextReturn _ | NextThrow _ | NextUnknown ->
+ ()
\ No newline at end of file
diff --git a/src/coro/coroToTexpr.ml b/src/coro/coroToTexpr.ml
new file mode 100644
index 00000000000..15cc5168063
--- /dev/null
+++ b/src/coro/coroToTexpr.ml
@@ -0,0 +1,418 @@
+open Globals
+open CoroTypes
+open CoroFunctions
+open Type
+open Texpr
+open CoroControl
+
+type coro_state = {
+ cs_id : int;
+ mutable cs_el : texpr list;
+}
+
+type coro_to_texpr_exprs = {
+ econtinuation : texpr;
+ ecompletion : texpr;
+ estate : texpr;
+ eresult : texpr;
+ egoto : texpr;
+ eerror : texpr;
+ etmp : texpr;
+}
+
+let make_suspending_call basic call econtinuation =
+ (* lose Coroutine type for the called function not to confuse further filters and generators *)
+ let tfun = match follow_with_coro call.cs_fun.etype with
+ | Coro (args, ret) ->
+ let args,ret = Common.expand_coro_type basic args ret in
+ TFun (args, ret)
+ | NotCoro _ ->
+ die "Unexpected coroutine type" __LOC__
+ in
+ let efun = { call.cs_fun with etype = tfun } in
+ let args = call.cs_args @ [ econtinuation ] in
+ mk (TCall (efun, args)) (basic.tcoro.suspension_result basic.tany) call.cs_pos
+
+let handle_locals ctx b cls states tf_args forbidden_vars econtinuation =
+ let module IntSet = Set.Make(struct
+ let compare a b = b - a
+ type t = int
+ end) in
+
+ (* function arguments are accessible from the initial state without hoisting needed, so set that now *)
+ let arg_state_set = IntSet.of_list [ (List.hd states).cs_id ] in
+ let var_usages = tf_args |> List.map (fun (v, _) -> v.v_id, arg_state_set) |> List.to_seq |> Hashtbl.of_seq in
+
+ (* First iteration, just add newly discovered local variables *)
+ (* After this var_usages will contain all arguments and local vars and the states sets will be just the creation state *)
+ (* We don't handle locals here so we don't poison the var_usage hashtbl with non local var data *)
+ List.iter (fun state ->
+ let rec loop e =
+ match e.eexpr with
+ | TVar (v, eo) ->
+ Option.may loop eo;
+ Hashtbl.replace var_usages v.v_id (IntSet.of_list [ state.cs_id ])
+ | _ ->
+ Type.iter loop e
+ in
+ List.iter loop state.cs_el
+ ) states;
+
+ (* Second interation, visit all locals and update any local variable state sets *)
+ List.iter (fun state ->
+ let rec loop e =
+ match e.eexpr with
+ | TLocal (v) ->
+ (match Hashtbl.find_opt var_usages v.v_id with
+ | Some set ->
+ Hashtbl.replace var_usages v.v_id (IntSet.add state.cs_id set)
+ | None ->
+ ())
+ | _ ->
+ Type.iter loop e
+ in
+ List.iter loop state.cs_el
+ ) states;
+
+ let is_used_across_states v_id =
+ let many_states set v_id =
+ IntSet.elements set |> List.length > 1 in
+ (* forbidden vars are things like the _hx_continuation variable, they should not be hoisted *)
+ let non_coro_var v_id =
+ forbidden_vars |> List.exists (fun id -> id = v_id) |> not in
+
+ match Hashtbl.find_opt var_usages v_id with
+ | Some set when many_states set v_id && non_coro_var v_id ->
+ true
+ | _ ->
+ false
+ in
+
+ let fields =
+ tf_args
+ |> List.filter_map (fun (v, _) ->
+ if is_used_across_states v.v_id then
+ Some (v.v_id, mk_field (Printf.sprintf "_hx_hoisted%i" v.v_id) v.v_type v.v_pos v.v_pos)
+ else
+ None)
+ |> List.to_seq
+ |> Hashtbl.of_seq in
+
+ (* Third iteration, create fields for vars used across states and remap access to those fields *)
+ List.iter (fun state ->
+ let rec loop e =
+ match e.eexpr with
+ | TVar (v, eo) when is_used_across_states v.v_id ->
+ let name = Printf.sprintf "_hx_hoisted%i" v.v_id in
+ let field = mk_field name v.v_type v.v_pos v.v_pos in
+
+ Hashtbl.replace fields v.v_id field;
+
+ begin match eo with
+ | None ->
+ (* We need an expression, so let's just emit `null`. The analyzer will clean this up. *)
+ b#null t_dynamic e.epos
+ | Some e ->
+ let efield = b#instance_field econtinuation cls [] field field.cf_type in
+ let einit =
+ match eo with
+ | None -> Builder.default_value v.v_type v.v_pos
+ | Some e -> Type.map_expr loop e in
+ b#assign efield einit
+ end
+ (* A local of a var should never appear before its declaration, right? *)
+ | TLocal (v) when is_used_across_states v.v_id ->
+ let field = Hashtbl.find fields v.v_id in
+
+ b#instance_field econtinuation cls [] field field.cf_type
+ | _ ->
+ Type.map_expr loop e
+ in
+ state.cs_el <- List.map loop state.cs_el
+ ) states;
+
+ (* We need to do this argument copying as the last thing we do *)
+ (* Doing it when the initial fields hashtbl is created will cause the third iterations TLocal to re-write them... *)
+ List.iter (fun (v, _) ->
+ if is_used_across_states v.v_id then
+ let initial = List.hd states in
+ let field = Hashtbl.find fields v.v_id in
+ let efield = b#instance_field econtinuation cls [] field field.cf_type in
+ let assign = b#assign efield (b#local v v.v_pos) in
+
+ initial.cs_el <- assign :: initial.cs_el) tf_args;
+ fields
+
+let block_to_texpr_coroutine ctx cb cont cls params tf_args forbidden_vars exprs p stack_item_inserter start_exception =
+ let {econtinuation;ecompletion;estate;eresult;egoto;eerror;etmp} = exprs in
+ let com = ctx.typer.com in
+ let b = ctx.builder in
+
+ let set_state id = b#assign egoto (b#int id p) in
+
+ let set_control (c : coro_control) = b#assign estate (CoroControl.mk_control com.basic c) in
+
+ let std_is e t =
+ let type_expr = mk (TTypeExpr (module_type_of_type t)) t_dynamic p in
+ Texpr.Builder.resolve_and_make_static_call com.std "isOfType" [e;type_expr] p
+ in
+
+ let ereturn = b#return econtinuation in
+
+ let mk_suspending_call call =
+ let p = call.cs_pos in
+ let base_continuation_field_on e cf t =
+ b#instance_field e com.basic.tcoro.suspension_result_class [com.basic.tany] cf t
+ in
+ let ecreatecoroutine = make_suspending_call com.basic call econtinuation in
+
+ let vcororesult = alloc_var VGenerated "_hx_tmp" (com.basic.tcoro.suspension_result com.basic.tany) p in
+ let ecororesult = b#local vcororesult p in
+ let cororesult_var = b#var_init vcororesult ecreatecoroutine in
+ let open ContTypes in
+ let esubject = base_continuation_field_on ecororesult cont.state cont.state.cf_type in
+ let esuspended = b#void_block [
+ set_control CoroPending;
+ ereturn;
+ ] in
+ let ereturned = b#assign etmp (base_continuation_field_on ecororesult cont.result com.basic.tany) in
+ let ethrown = b#void_block [
+ b#assign eresult (* TODO: wrong type? *) (base_continuation_field_on ecororesult cont.result com.basic.tany);
+ b#assign etmp (base_continuation_field_on ecororesult cont.error cont.error.cf_type);
+ b#break p;
+ ] in
+ let estate_switch = CoroControl.make_control_switch com.basic esubject esuspended ereturned ethrown p in
+ [
+ stack_item_inserter call.cs_pos;
+ cororesult_var;
+ estate_switch;
+ ]
+ in
+
+ let states = ref [] in
+
+ let init_state = cb.cb_id in
+
+ let make_state id el = {
+ cs_id = id;
+ cs_el = el;
+ } in
+
+ (* TODO: this sucks a bit and its usage isn't much better *)
+ let wrap_thrown,get_caught = match com.basic.texception with
+ | TInst(c,_) ->
+ (fun e -> Texpr.Builder.resolve_and_make_static_call c "thrown" [e] e.epos),
+ (fun e -> Texpr.Builder.resolve_and_make_static_call c "caught" [e] e.epos)
+ | _ ->
+ die "" __LOC__
+ in
+ let eif_error =
+ let el = if ctx.throw then
+ [b#throw eerror]
+ else [
+ b#assign etmp eerror;
+ b#break p;
+ ] in
+ let e_then = b#void_block el in
+ b#if_then
+ (b#binop OpNotEq eerror (b#null eerror.etype p) com.basic.tbool)
+ e_then
+ in
+
+ let exc_state_map = Array.init ctx.next_block_id (fun _ -> ref []) in
+ let generate cb =
+ let el = get_block_exprs cb in
+
+ let add_state next_id extra_el =
+ let el = el in
+ let el = match next_id with
+ | None ->
+ el
+ | Some id ->
+ el @ [set_state id]
+ in
+ let el = if has_block_flag cb CbResumeState then
+ eif_error :: el
+ else
+ el
+ in
+ let el = el @ extra_el in
+ states := (make_state cb.cb_id el) :: !states;
+ begin match cb.cb_catch with
+ | None ->
+ ()
+ | Some cb' ->
+ let r = exc_state_map.(cb'.cb_id) in
+ r := cb.cb_id :: !r
+ end;
+ cb.cb_id
+ in
+ match cb.cb_next with
+ | NextSuspend (call, cb_next) ->
+ let ecallcoroutine = mk_suspending_call call in
+ add_state (Option.map (fun cb_next -> cb_next.cb_id) cb_next) ecallcoroutine;
+ | NextUnknown ->
+ add_state (Some (-1)) [set_control CoroReturned; ereturn]
+ | NextFallThrough cb_next | NextGoto cb_next | NextBreak cb_next | NextContinue cb_next ->
+ add_state (Some cb_next.cb_id) []
+ | NextReturnVoid ->
+ add_state (Some (-1)) [ set_control CoroReturned; ereturn ]
+ | NextReturn e ->
+ add_state (Some (-1)) [ set_control CoroReturned; b#assign eresult e; ereturn ]
+ | NextThrow e1 ->
+ if ctx.throw then
+ add_state None ([stack_item_inserter e1.epos; start_exception (b#bool true p); b#throw e1])
+ else
+ add_state None ([stack_item_inserter e1.epos; start_exception (b#bool true p); b#assign etmp e1; b#break p ])
+ | NextSub (cb_sub,cb_next) ->
+ add_state (Some cb_sub.cb_id) []
+
+ | NextIfThen (econd,cb_then,cb_next) ->
+ let eif = b#if_then_else econd (set_state cb_then.cb_id) (set_state cb_next.cb_id) com.basic.tint in
+ add_state None [eif]
+
+ | NextIfThenElse (econd,cb_then,cb_else,cb_next) ->
+ let eif = b#if_then_else econd (set_state cb_then.cb_id) (set_state cb_else.cb_id) com.basic.tint in
+ add_state None [eif]
+
+ | NextSwitch(switch,cb_next) ->
+ let esubj = switch.cs_subject in
+ let ecases = List.map (fun (patterns,cb) ->
+ {case_patterns = patterns;case_expr = set_state cb.cb_id}
+ ) switch.cs_cases in
+ let next_id = match switch.cs_default with
+ | Some cb ->
+ Some (set_state cb.cb_id)
+ | None ->
+ Option.map (fun cb_next -> set_state cb_next.cb_id) cb_next
+ in
+ let eswitch = mk_switch esubj ecases next_id true in
+ let eswitch = mk (TSwitch eswitch) com.basic.tvoid p in
+
+ add_state None [eswitch]
+
+ | NextWhile (e_cond,cb_body,cb_next) ->
+ add_state (Some cb_body.cb_id) []
+
+ | NextTry (cb_try,catch,cb_next) ->
+ let new_exc_state_id = catch.cc_cb.cb_id in
+ let erethrow = match catch.cc_cb.cb_catch with
+ | Some cb ->
+ set_state cb.cb_id
+ | None ->
+ b#void_block [
+ b#break p
+ ]
+ in
+ let eif =
+ List.fold_left (fun enext (vcatch,cb_catch) ->
+ match follow vcatch.v_type with
+ | TDynamic _ ->
+ set_state cb_catch.cb_id (* no next *)
+ | t ->
+ let etypecheck = std_is etmp vcatch.v_type in
+ b#if_then_else etypecheck (set_state cb_catch.cb_id) enext com.basic.tvoid
+ ) erethrow (List.rev catch.cc_catches)
+ in
+ states := (make_state new_exc_state_id [eif]) :: !states;
+ add_state (Some cb_try.cb_id) []
+ in
+ let rec loop cb =
+ if not (has_block_flag cb CbGenerated) then begin
+ add_block_flag cb CbGenerated;
+ ignore(generate cb);
+ coro_iter loop cb;
+ end
+ in
+ loop cb;
+
+ let states = !states in
+ let states = states |> List.sort (fun state1 state2 -> state1.cs_id - state2.cs_id) in
+
+ let fields = handle_locals ctx b cls states tf_args forbidden_vars econtinuation in
+
+ let ethrow = b#void_block [
+ b#assign etmp (b#string "Invalid coroutine state" p);
+ b#break p
+ ] in
+
+ let switch =
+ let cases = List.map (fun state ->
+ {case_patterns = [b#int state.cs_id p];
+ case_expr = b#void_block state.cs_el;
+ }) states in
+ mk_switch egoto cases (Some ethrow) true
+ in
+ let eswitch = mk (TSwitch switch) com.basic.tvoid p in
+
+ let eloop = mk (TWhile (b#bool true p, eswitch, NormalWhile)) com.basic.tvoid p in
+
+ let etry = if ctx.nothrow || (ctx.throw && not ctx.has_catch) then
+ eloop
+ else
+ mk (TTry (
+ eloop,
+ [
+ let vcaught = alloc_var VGenerated "e" t_dynamic p in
+ let ecaught = b#local vcaught p in
+ let e = b#void_block [
+ start_exception (b#bool false p);
+ b#assign etmp ecaught
+ ] in
+ (vcaught,e)
+ ]
+ )) com.basic.tvoid p
+ in
+
+ let eexchandle =
+ let cases = DynArray.create () in
+ Array.iteri (fun i l -> match !l with
+ | [] ->
+ ()
+ | l ->
+ let patterns = List.map (fun i -> b#int i p) l in
+ let expr = b#void_block [
+ set_state i;
+ ] in
+ DynArray.add cases {case_patterns = patterns; case_expr = expr};
+ ) exc_state_map;
+ let el = if ctx.throw then [
+ b#throw etmp
+ ] else begin
+ let field = PMap.find "buildCallStack" com.basic.tcoro.base_continuation_class.cl_fields in
+ let eaccess = b#instance_field econtinuation com.basic.tcoro.base_continuation_class params field field.cf_type in
+ let ewrapped_call = mk (TCall (eaccess, [ ])) com.basic.tvoid p in
+ [
+ ewrapped_call;
+ b#assign eerror (wrap_thrown etmp);
+ set_control CoroThrown;
+ ereturn;
+ ]
+ end in
+ let default = b#void_block el in
+ if DynArray.empty cases then
+ default
+ else begin
+ let switch = {
+ switch_subject = egoto;
+ switch_cases = DynArray.to_list cases;
+ switch_default = Some default;
+ switch_exhaustive = true
+ } in
+ mk (TSwitch switch) com.basic.tvoid p
+ end
+ in
+
+ let etry = b#void_block [
+ etry;
+ eexchandle;
+ ] in
+
+ let eloop = if ctx.has_catch then
+ mk (TWhile (b#bool true p, etry, NormalWhile)) com.basic.tvoid p
+ else
+ (* If there is no catch we don't need to pseudo-goto back into the state loop, so we don't need a control loop. *)
+ etry
+ in
+
+ eloop, init_state, fields |> Hashtbl.to_seq_values |> List.of_seq
diff --git a/src/coro/coroTypes.ml b/src/coro/coroTypes.ml
new file mode 100644
index 00000000000..1699f09456d
--- /dev/null
+++ b/src/coro/coroTypes.ml
@@ -0,0 +1,74 @@
+open Globals
+open Type
+
+type coro_block = {
+ mutable cb_id : int;
+ cb_el : texpr DynArray.t;
+ cb_typepos : (Type.t * pos) option;
+ mutable cb_catch : coro_block option;
+ mutable cb_next : coro_next;
+ mutable cb_flags : int;
+}
+
+and coro_block_next = coro_block option
+
+and coro_next =
+ | NextUnknown
+ | NextSub of coro_block * coro_block_next
+ | NextReturnVoid
+ | NextReturn of texpr
+ | NextThrow of texpr
+ | NextIfThen of texpr * coro_block * coro_block
+ | NextIfThenElse of texpr * coro_block * coro_block * coro_block_next
+ | NextSwitch of coro_switch * coro_block_next
+ | NextWhile of texpr * coro_block * coro_block_next
+ | NextTry of coro_block * coro_catch * coro_block_next
+ | NextSuspend of coro_suspend * coro_block_next
+ (* graph connections from here on, careful with traversal *)
+ | NextBreak of coro_block
+ | NextContinue of coro_block
+ | NextFallThrough of coro_block
+ | NextGoto of coro_block
+
+and coro_switch = {
+ cs_subject : texpr;
+ cs_cases : (texpr list * coro_block) list;
+ cs_default : coro_block option;
+ cs_exhaustive : bool;
+}
+
+and coro_catch = {
+ cc_cb : coro_block;
+ cc_catches : (tvar * coro_block) list;
+}
+
+and coro_suspend = {
+ cs_fun : texpr;
+ cs_args : texpr list;
+ cs_pos : pos;
+}
+
+type coro_ctx = {
+ builder : CoroElsewhere.texpr_builder;
+ typer : Typecore.typer;
+ coro_debug : bool;
+ optimize : bool;
+ allow_tco : bool;
+ throw : bool;
+ nothrow : bool;
+ mutable vthis : tvar option;
+ mutable next_block_id : int;
+ mutable current_catch : coro_block option;
+ mutable has_catch : bool;
+}
+
+type cb_flag =
+ | CbEmptyMarked
+ | CbForwardMarked
+ | CbTcoChecked
+ | CbReindexed
+ | CbGenerated
+ | CbSuspendState
+ | CbResumeState
+
+exception CoroTco of coro_block
\ No newline at end of file
diff --git a/src/generators/cpp/gen/cppGenClassImplementation.ml b/src/generators/cpp/gen/cppGenClassImplementation.ml
index 3490ca70338..d0bd6d05e68 100644
--- a/src/generators/cpp/gen/cppGenClassImplementation.ml
+++ b/src/generators/cpp/gen/cppGenClassImplementation.ml
@@ -831,8 +831,8 @@ let generate_managed_class base_ctx tcpp_class =
output_cpp "#endif\n\n");
let generate_script_function isStatic field scriptName callName =
- match follow field.cf_type with
- | TFun (args, return_type) when not (is_data_member field) ->
+ if not (is_data_member field) then
+ let gen (args, return_type) =
let isTemplated = not isStatic in
if isTemplated then output_cpp "\ntemplate";
output_cpp
@@ -880,43 +880,51 @@ let generate_managed_class base_ctx tcpp_class =
if ret <> "v" then output_cpp ")";
output_cpp ";\n}\n";
signature
- | _ -> ""
+ in
+ match follow_with_coro field.cf_type with
+ | Coro (args, return) -> Common.expand_coro_type ctx.ctx_common.basic args return |> gen
+ | NotCoro TFun (args, return) -> gen (args, return)
+ | _ -> ""
+ else
+ ""
in
if scriptable then (
let dump_script_func idx func =
- match func.tcf_field.cf_type with
- | TFun (f_args, _) ->
- let args = print_tfun_arg_list true f_args in
- let return_type = type_to_string func.tcf_func.tf_type in
- let ret = if return_type = "Void" || return_type = "void" then " " else "return " in
- let vtable = Printf.sprintf "__scriptVTable[%i]" (idx + 1) in
-
- Printf.sprintf "\t%s %s(%s) {\n" return_type func.tcf_name args |> output_cpp;
- Printf.sprintf ("\tif (%s) {\n") vtable |> output_cpp;
- output_cpp "\t\t::hx::CppiaCtx *__ctx = ::hx::CppiaCtx::getCurrent();\n";
- output_cpp "\t\t::hx::AutoStack __as(__ctx);\n";
- output_cpp ("\t\t__ctx->pushObject( this );\n");
-
- List.iter
- (fun (name, opt, t) ->
- Printf.sprintf "\t\t__ctx->push%s(%s);\n" (CppCppia.script_type t opt) (keyword_remap name) |> output_cpp)
- f_args;
+ let f_args =
+ match follow_with_coro func.tcf_field.cf_type with
+ | Coro (args, return) -> Common.expand_coro_type ctx.ctx_common.basic args return |> fst
+ | NotCoro TFun (args, _) -> args
+ | _ -> abort "expected function type to be tfun" func.tcf_field.cf_pos
+ in
+ let args = print_tfun_arg_list true f_args in
+ let return_type = type_to_string func.tcf_func.tf_type in
+ let ret = if return_type = "Void" || return_type = "void" then " " else "return " in
+ let vtable = Printf.sprintf "__scriptVTable[%i]" (idx + 1) in
- output_cpp
- ("\t\t" ^ ret ^ "__ctx->run" ^ CppCppia.script_type func.tcf_func.tf_type false ^ "(" ^ vtable ^ ");\n");
- output_cpp ("\t} else " ^ ret);
+ Printf.sprintf "\t%s %s(%s) {\n" return_type func.tcf_name args |> output_cpp;
+ Printf.sprintf ("\tif (%s) {\n") vtable |> output_cpp;
+ output_cpp "\t\t::hx::CppiaCtx *__ctx = ::hx::CppiaCtx::getCurrent();\n";
+ output_cpp "\t\t::hx::AutoStack __as(__ctx);\n";
+ output_cpp ("\t\t__ctx->pushObject( this );\n");
+
+ List.iter
+ (fun (name, opt, t) ->
+ Printf.sprintf "\t\t__ctx->push%s(%s);\n" (CppCppia.script_type t opt) (keyword_remap name) |> output_cpp)
+ f_args;
- let names = List.map (fun (n, _, _) -> keyword_remap n) f_args in
+ output_cpp
+ ("\t\t" ^ ret ^ "__ctx->run" ^ CppCppia.script_type func.tcf_func.tf_type false ^ "(" ^ vtable ^ ");\n");
+ output_cpp ("\t} else " ^ ret);
- output_cpp
- (class_name ^ "::" ^ func.tcf_name ^ "(" ^ String.concat "," names ^ ");");
+ let names = List.map (fun (n, _, _) -> keyword_remap n) f_args in
- if return_type <> "void" then output_cpp "return null();";
+ output_cpp
+ (class_name ^ "::" ^ func.tcf_name ^ "(" ^ String.concat "," names ^ ");");
- output_cpp "}\n";
- | _ ->
- abort "expected function type to be tfun" func.tcf_field.cf_pos
+ if return_type <> "void" then output_cpp "return null();";
+
+ output_cpp "}\n"
in
let script_name = class_name ^ "__scriptable" in
diff --git a/src/generators/cpp/gen/cppReferences.ml b/src/generators/cpp/gen/cppReferences.ml
index 294a72febaa..1e3d8e7bbde 100644
--- a/src/generators/cpp/gen/cppReferences.ml
+++ b/src/generators/cpp/gen/cppReferences.ml
@@ -89,39 +89,45 @@ let find_referenced_types_flags ctx obj filter super_deps constructor_deps heade
let rec visit_type in_type =
if not (List.exists (fun t2 -> Type.fast_eq in_type t2) !visited) then (
visited := in_type :: !visited;
- (match follow in_type with
- | TMono r -> ( match r.tm_type with None -> () | Some t -> visit_type t)
- | TEnum (enum, _) -> (
- match is_extern_enum enum with
- | true -> add_extern_enum enum
- | false -> add_type enum.e_path)
- (* If a class has a template parameter, then we treat it as dynamic - except
- for the Array, Class, FastIterator or Pointer classes, for which we do a fully typed object *)
- | TInst (klass, params) -> (
- match klass.cl_path with
- | [], "Array"
- | [], "Class"
- | [ "cpp" ], "FastIterator"
- | [ "cpp" ], "Pointer"
- | [ "cpp" ], "ConstPointer"
- | [ "cpp" ], "Function"
- | [ "cpp" ], "RawPointer"
- | [ "cpp" ], "RawConstPointer" ->
- List.iter visit_type params
- | _ when is_native_gen_class klass -> add_native_gen_class klass
- | _ when is_extern_class klass ->
- add_extern_class klass;
- List.iter visit_type params
- | _ -> (
- match klass.cl_kind with
- | KTypeParameter _ -> ()
- | _ -> add_type klass.cl_path))
- | TAbstract (a, params) when is_scalar_abstract a ->
- add_extern_type (TAbstractDecl a)
- | TFun (args, haxe_type) ->
- visit_type haxe_type;
- List.iter (fun (_, _, t) -> visit_type t) args
- | _ -> ());
+ (match follow_with_coro in_type with
+ | Coro (args, return) ->
+ let args, return = Common.expand_coro_type ctx.ctx_common.basic args return in
+ visit_type return;
+ List.iter (fun (_, _, t) -> visit_type t) args
+ | NotCoro t ->
+ (match follow t with
+ | TMono r -> ( match r.tm_type with None -> () | Some t -> visit_type t)
+ | TEnum (enum, _) -> (
+ match is_extern_enum enum with
+ | true -> add_extern_enum enum
+ | false -> add_type enum.e_path)
+ (* If a class has a template parameter, then we treat it as dynamic - except
+ for the Array, Class, FastIterator or Pointer classes, for which we do a fully typed object *)
+ | TInst (klass, params) -> (
+ match klass.cl_path with
+ | [], "Array"
+ | [], "Class"
+ | [ "cpp" ], "FastIterator"
+ | [ "cpp" ], "Pointer"
+ | [ "cpp" ], "ConstPointer"
+ | [ "cpp" ], "Function"
+ | [ "cpp" ], "RawPointer"
+ | [ "cpp" ], "RawConstPointer" ->
+ List.iter visit_type params
+ | _ when is_native_gen_class klass -> add_native_gen_class klass
+ | _ when is_extern_class klass ->
+ add_extern_class klass;
+ List.iter visit_type params
+ | _ -> (
+ match klass.cl_kind with
+ | KTypeParameter _ -> ()
+ | _ -> add_type klass.cl_path))
+ | TAbstract (a, params) when is_scalar_abstract a ->
+ add_extern_type (TAbstractDecl a)
+ | TFun (args, haxe_type) ->
+ visit_type haxe_type;
+ List.iter (fun (_, _, t) -> visit_type t) args
+ | _ -> ()));
visited := List.tl !visited)
in
let visit_params expression =
diff --git a/src/generators/genhl.ml b/src/generators/genhl.ml
index 663de6dd440..d48d82bd3e0 100644
--- a/src/generators/genhl.ml
+++ b/src/generators/genhl.ml
@@ -480,6 +480,14 @@ let rec to_type ?tref ctx t =
| ["hl"], "GUID" -> HGUID
| ["hl"], "NativeArray" -> HArray (to_type ctx (List.hd pl))
| ["haxe";"macro"], "Position" -> HAbstract ("macro_pos", alloc_string ctx "macro_pos")
+ | ["haxe";"coro"], "Coroutine" ->
+ begin match pl with
+ | [TFun(args,ret)] ->
+ let args,ret = Common.expand_coro_type ctx.com.basic args ret in
+ to_type ctx (TFun(args,ctx.com.basic.tcoro.suspension_result ret))
+ | _ ->
+ die "" __LOC__
+ end
| _ -> failwith ("Unknown core type " ^ s_type_path a.a_path))
else
get_rec_cache ctx t
diff --git a/src/generators/genjvm.ml b/src/generators/genjvm.ml
index 01ca8df003f..077ad9c46ab 100644
--- a/src/generators/genjvm.ml
+++ b/src/generators/genjvm.ml
@@ -168,6 +168,14 @@ let rec jsignature_of_type gctx stack t =
end
| [],"EnumValue" ->
java_enum_sig object_sig
+ | ["haxe";"coro"],"Coroutine" ->
+ begin match tl with
+ | [TFun(args,ret)] ->
+ let args,ret = Common.expand_coro_type gctx.gctx.basic args ret in
+ jsignature_of_type (TFun(args,gctx.gctx.basic.tcoro.suspension_result ret))
+ | _ ->
+ die "" __LOC__
+ end
| _ ->
if Meta.has Meta.CoreType a.a_meta then
TObject(a.a_path,List.map jtype_argument_of_type tl)
@@ -197,11 +205,12 @@ let rec jsignature_of_type gctx stack t =
| TInst(c,tl) -> TObject(c.cl_path,List.map jtype_argument_of_type tl)
| TEnum(en,tl) ->
TObject(en.e_path,List.map jtype_argument_of_type tl)
- | TFun(tl,tr) -> method_sig (List.map (fun (_,o,t) ->
- let jsig = jsignature_of_type t in
- let jsig = if o then get_boxed_type jsig else jsig in
- jsig
- ) tl) (return_of_type gctx stack tr)
+ | TFun(tl,tr) ->
+ method_sig (List.map (fun (_,o,t) ->
+ let jsig = jsignature_of_type t in
+ let jsig = if o then get_boxed_type jsig else jsig in
+ jsig
+ ) tl) (return_of_type gctx stack tr)
| TAnon an -> object_sig
| TType(td,tl) ->
begin match gctx.typedef_interfaces#get_interface_class td.t_path with
@@ -765,8 +774,11 @@ class texpr_to_jvm
method read cast e1 fa =
let read_static_closure path cf =
- let args,ret = match follow cf.cf_type with
- | TFun(tl,tr) -> List.map (fun (n,_,t) -> n,self#vtype t) tl,(return_of_type gctx tr)
+ let args,ret = match follow_with_coro cf.cf_type with
+ | NotCoro TFun(tl,tr) -> List.map (fun (n,_,t) -> n,self#vtype t) tl,(return_of_type gctx tr)
+ | Coro (tl,tr) ->
+ let tl,tr = Common.expand_coro_type gctx.gctx.basic tl tr in
+ List.map (fun (n,_,t) -> n,self#vtype t) tl,(return_of_type gctx tr)
| _ -> die "" __LOC__
in
self#read_static_closure path cf.cf_name args ret cf.cf_type
@@ -1558,9 +1570,12 @@ class texpr_to_jvm
(* calls *)
method call_arguments ?(cast=true) t el =
- let tl,tr = match follow t with
- | TFun(tl,tr) ->
+ let tl,tr = match follow_with_coro t with
+ | NotCoro (TFun(tl,tr)) ->
tl,return_of_type gctx tr
+ | Coro(args,ret) ->
+ let args,ret = Common.expand_coro_type gctx.gctx.basic args ret in
+ args,return_of_type gctx ret
| _ ->
List.map (fun e -> ("",false,e.etype)) el,Some (object_sig)
in
@@ -1963,7 +1978,8 @@ class texpr_to_jvm
if not jm#is_terminated then self#texpr' ret e
method texpr' ret e =
- code#set_line (Lexer.get_error_line e.epos);
+ if e.epos.pmin >= 0 then
+ code#set_line (Lexer.get_error_line e.epos);
match e.eexpr with
| TVar(v,Some e1) ->
self#texpr (rvalue_type gctx v.v_type (Some v.v_name)) e1;
@@ -2428,8 +2444,9 @@ class tclass_to_jvm gctx c = object(self)
maybe_make_bridge cf_impl.cf_name jsig_super jsig_impl
in
let find_overload map_type c cf =
- let tl = match follow (map_type cf.cf_type) with
- | TFun(tl,_) -> tl
+ let tl = match follow_with_coro (map_type cf.cf_type) with
+ | Coro (tl, _) -> tl
+ | NotCoro TFun(tl,_) -> tl
| _ -> die "" __LOC__
in
OverloadResolution.resolve_instance_overload false map_type c cf.cf_name (List.map (fun (_,_,t) -> Texpr.Builder.make_null t null_pos) tl)
diff --git a/src/generators/genneko.ml b/src/generators/genneko.ml
index 217cbf941c4..b534423dd17 100644
--- a/src/generators/genneko.ml
+++ b/src/generators/genneko.ml
@@ -235,9 +235,7 @@ and gen_expr ctx e =
| TBinop (op,e1,e2) ->
gen_binop ctx p op e1 e2
| TField (e2,FClosure (_,f)) ->
- (match follow e.etype with
- | TFun (args,_) ->
- let n = List.length args in
+ let emit n =
if n > 5 then Error.abort "Cannot create closure with more than 5 arguments" e.epos;
let tmp = ident p "@tmp" in
EBlock [
@@ -247,6 +245,14 @@ and gen_expr ctx e =
else
call p (ident p ("@closure" ^ string_of_int n)) [tmp;ident p "@fun"]
] , p
+ in
+ (match follow_with_coro e.etype with
+ | NotCoro TFun (args,_) ->
+ let n = List.length args in
+ emit n
+ | Coro (args,_) ->
+ let n = List.length args in
+ emit (n + 1)
| _ -> die "" __LOC__)
| TEnumParameter (e,_,i) ->
EArray (field p (gen_expr ctx e) "args",int p i),p
diff --git a/src/generators/genphp7.ml b/src/generators/genphp7.ml
index 21dff91f866..e0f48c9cf3b 100644
--- a/src/generators/genphp7.ml
+++ b/src/generators/genphp7.ml
@@ -410,9 +410,10 @@ let rec needs_temp_var expr =
(**
@return (arguments_list, return_type)
*)
-let get_function_signature (field:tclass_field) : (string * bool * Type.t) list * Type.t =
- match follow field.cf_type with
- | TFun (args, return_type) -> (args, return_type)
+let get_function_signature basic (field:tclass_field) : (string * bool * Type.t) list * Type.t =
+ match follow_with_coro field.cf_type with
+ | Coro (args, return_type) -> Common.expand_coro_type basic args return_type
+ | NotCoro TFun (args, return_type) -> args, return_type
| _ -> fail field.cf_pos __LOC__
(**
@@ -598,9 +599,16 @@ let fix_tsignature_args args =
(**
Inserts `null`s if there are missing optional args before empty rest arguments.
*)
-let fix_call_args callee_type exprs =
- match follow callee_type with
- | TFun (args,_) ->
+let fix_call_args basic callee_type exprs =
+ let args =
+ match follow_with_coro callee_type with
+ | Coro (args, return_type) -> Some (Common.expand_coro_type basic args return_type |> fst)
+ | NotCoro TFun (args,_) -> Some args
+ | _ -> None
+ in
+
+ match args with
+ | Some args ->
(match List.rev args with
| (_,_,t) :: args_rev when is_rest_type t && List.length args_rev > List.length exprs ->
let rec loop args exprs =
@@ -612,7 +620,7 @@ let fix_call_args callee_type exprs =
loop args exprs
| _ -> exprs
)
- | _ -> exprs
+ | None -> exprs
(**
Escapes all "$" chars and encloses `str` into double quotes
@@ -1490,8 +1498,15 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
| current :: _ ->
match self#parent_expr with
| Some { eexpr = TCall (target, params) } when current != (reveal_expr target) ->
- (match follow target.etype with
- | TFun (args,_) ->
+ let args =
+ match follow_with_coro target.etype with
+ | Coro (args, return_type) -> Some (Common.expand_coro_type ctx.pgc_common.basic args return_type |> fst)
+ | NotCoro TFun (args,_) -> Some args
+ | _ -> None
+ in
+
+ (match args with
+ | Some args ->
let rec check args params =
match args, params with
| (_, _, t) :: _, param :: _ when current == (reveal_expr param) ->
@@ -2302,7 +2317,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
| FInstance (_, _, ({ cf_kind = Method _ } as field))
| FClosure (_, ({ cf_kind = Method _ } as field)) ->
self#write ((self#use hxstring_type_path) ^ "::" ^ (field_name field) ^ "(");
- write_args self#write self#write_expr (fix_call_args field.cf_type (expr :: args));
+ write_args self#write self#write_expr (fix_call_args ctx.pgc_common.basic field.cf_type (expr :: args));
self#write ")"
| _ ->
let msg =
@@ -2651,7 +2666,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
if not !no_call then
begin
self#write "(";
- write_args self#write self#write_expr (fix_call_args target_expr.etype args);
+ write_args self#write self#write_expr (fix_call_args ctx.pgc_common.basic target_expr.etype args);
self#write ")"
end
(**
@@ -2718,7 +2733,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
self#write ("new " ^ (self#use ~prefix:needs_php_prefix inst_class.cl_path) ^ "(");
let args =
match inst_class.cl_constructor with
- | Some field -> fix_call_args field.cf_type args
+ | Some field -> fix_call_args ctx.pgc_common.basic field.cf_type args
| None -> args
in
write_args self#write self#write_expr args;
@@ -3455,8 +3470,15 @@ class class_builder ctx (cls:tclass) =
| Some (cls, _) ->
let fields = if is_static then cls.cl_statics else cls.cl_fields in
try
- match (PMap.find name fields).cf_type with
- | TFun (args,_) ->
+ let args =
+ match follow_with_coro (PMap.find name fields).cf_type with
+ | Coro (args, return_type) -> Some (Common.expand_coro_type ctx.pgc_common.basic args return_type |> fst)
+ | NotCoro TFun (args,_) -> Some args
+ | _ -> None
+ in
+
+ match args with
+ | Some args ->
let rec count args mandatory total =
match args with
| [] ->
@@ -3793,7 +3815,7 @@ class class_builder ctx (cls:tclass) =
self#validate_method_name field;
writer#reset;
writer#indent 1;
- let (args, return_type) = get_function_signature field in
+ let (args, return_type) = get_function_signature ctx.pgc_common.basic field in
List.iter (fun (arg_name, _, _) -> writer#declared_local_var arg_name) args;
self#write_doc (DocMethod (args, return_type, (gen_doc_text_opt field.cf_doc))) field.cf_meta;
writer#write_indentation;
@@ -3820,13 +3842,14 @@ class class_builder ctx (cls:tclass) =
self#validate_method_name field;
writer#reset;
writer#indent 1;
- let (args, return_type) = get_function_signature field in
+ let (args, return_type) = get_function_signature ctx.pgc_common.basic field in
List.iter (fun (arg_name, _, _) -> writer#declared_local_var arg_name) args;
self#write_doc (DocMethod (args, return_type, (gen_doc_text_opt field.cf_doc))) field.cf_meta;
let visibility_kwd = get_visibility field.cf_meta in
writer#write_with_indentation (visibility_kwd ^ " function " ^ (field_name field));
(match field.cf_expr with
| None -> (* interface *)
+ (* let args, _ = Common.expand_coro_type ctx.pgc_common.basic args return_type in *)
writer#write " (";
write_args writer#write (writer#write_arg true) (fix_tsignature_args args);
writer#write ");\n";
diff --git a/src/optimization/analyzerTypes.ml b/src/optimization/analyzerTypes.ml
index 5ad70b6d7f2..acec94b0328 100644
--- a/src/optimization/analyzerTypes.ml
+++ b/src/optimization/analyzerTypes.ml
@@ -83,20 +83,14 @@ module BasicBlock = struct
ss_exhaustive : bool;
}
- and suspend_call = {
- efun : texpr; (* coroutine function expression *)
- args : texpr list; (* call arguments without the continuation *)
- pos : pos; (* call position *)
- }
-
and terminator_kind =
- | TermNone
- | TermCondBranch of texpr
- | TermReturn of pos
- | TermReturnValue of texpr * pos
- | TermBreak of pos
- | TermContinue of pos
- | TermThrow of texpr * pos
+ | TermNone
+ | TermCondBranch of texpr
+ | TermReturn of pos
+ | TermReturnValue of texpr * pos
+ | TermBreak of pos
+ | TermContinue of pos
+ | TermThrow of texpr * pos
and t = {
bb_id : int; (* The unique ID of the block *)
diff --git a/src/typing/callUnification.ml b/src/typing/callUnification.ml
index 62208cc6a76..ed8fa5162aa 100644
--- a/src/typing/callUnification.ml
+++ b/src/typing/callUnification.ml
@@ -177,7 +177,7 @@ let unify_call_args ctx el args r callp inline force_inline in_overload =
in
let el = try loop el args with exc -> restore(); raise exc; in
restore();
- el,TFun(args,r)
+ el
type overload_kind =
| OverloadProper (* @:overload or overload *)
@@ -286,10 +286,9 @@ let unify_field_call ctx fa el_typed el p inline =
let attempt_call cf in_overload =
let monos = Monomorph.spawn_constrained_monos map cf.cf_params in
let t = map (apply_params cf.cf_params monos cf.cf_type) in
- match follow t with
- | TFun(args,ret) ->
+ let make args ret coro =
let args_typed,args = unify_typed_args ctx tmap args el_typed p in
- let el,_ =
+ let el =
try
unify_call_args ctx el args ret p inline is_forced_inline in_overload
with DisplayException.DisplayException de ->
@@ -297,13 +296,23 @@ let unify_field_call ctx fa el_typed el p inline =
in
(* here *)
let el = el_typed @ el in
- let tf = TFun(args_typed @ args,ret) in
+ let args = (args_typed @ args) in
+ let tf = if coro then ctx.t.tcoro.tcoro args ret else TFun(args,ret) in
let mk_call () =
let ef = mk (TField(fa.fa_on,FieldAccess.apply_fa cf fa.fa_host)) t fa.fa_pos in
!make_call_ref ctx ef el ret ~force_inline:inline p
in
make_field_call_candidate el ret monos tf cf (mk_call,extract_delayed_display())
- | t ->
+ in
+ match follow_with_coro t with
+ | Coro(args,ret) when not (TyperManager.is_coroutine_context ctx) ->
+ let args, ret = expand_coro_type ctx.com.basic args ret in
+ make args ret false
+ | Coro(args,ret) ->
+ make args ret true
+ | NotCoro (TFun(args,ret)) ->
+ make args ret false
+ | NotCoro t ->
raise_typing_error (s_type (print_context()) t ^ " cannot be called") p
in
let unknown_ident_error = ref None in
@@ -542,14 +551,21 @@ object(self)
in
mk (TCall (e,el)) t p
in
- let rec loop t = match follow t with
- | TFun (args,r) ->
+ let make args ret =
let args_typed,args_left = unify_typed_args ctx (fun t -> t) args el_typed p in
- let el, tfunc = unify_call_args ctx el args_left r p false false false in
+ let el = unify_call_args ctx el args_left ret p false false false in
let el = el_typed @ el in
- let r = match tfunc with TFun(_,r) -> r | _ -> die "" __LOC__ in
- mk (TCall (e,el)) r p
- | TAbstract(a,tl) as t ->
+ mk (TCall (e,el)) ret p
+ in
+ let rec loop t = match follow_with_coro t with
+ | Coro(args,ret) when not (TyperManager.is_coroutine_context ctx) ->
+ let args, ret = expand_coro_type ctx.com.basic args ret in
+ make args ret
+ | Coro(args,ret) ->
+ make args ret
+ | NotCoro(TFun(args,ret)) ->
+ make args ret
+ | NotCoro(TAbstract(a,tl) as t) ->
let check_callable () =
if Meta.has Meta.Callable a.a_meta then
loop (Abstract.get_underlying_type a tl)
@@ -564,12 +580,12 @@ object(self)
| _ ->
check_callable();
end
- | TMono _ ->
+ | NotCoro (TMono _)->
let t = mk_mono() in
let el = el_typed @ List.map (fun e -> type_expr ctx e WithType.value) el in
unify ctx (tfun (List.map (fun e -> e.etype) el) t) e.etype e.epos;
mk (TCall (e,el)) t p
- | t ->
+ | NotCoro t ->
default t
in
loop e.etype
diff --git a/src/typing/macroContext.ml b/src/typing/macroContext.ml
index 7b79c9746e6..84556a400bc 100644
--- a/src/typing/macroContext.ml
+++ b/src/typing/macroContext.ml
@@ -951,7 +951,7 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
incr index;
(EArray ((EArrayDecl [e],p),(EConst (Int (string_of_int (!index), None)),p)),p)
) el in
- let elt = fst (CallUnification.unify_call_args mctx constants (List.map fst eargs) t_dynamic p false false false) in
+ let elt = CallUnification.unify_call_args mctx constants (List.map fst eargs) t_dynamic p false false false in
List.map2 (fun ((n,_,t),mct) e ->
let e, et = (match e.eexpr with
(* get back our index and real expression *)
@@ -1025,7 +1025,7 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
e
let call_macro mctx args margs call p =
- let el, _ = CallUnification.unify_call_args mctx args margs t_dynamic p false false false in
+ let el = CallUnification.unify_call_args mctx args margs t_dynamic p false false false in
call (List.map (fun e -> try Interp.make_const e with Exit -> raise_typing_error "Argument should be a constant" e.epos) el)
let resolve_init_macro com e =
diff --git a/src/typing/typeload.ml b/src/typing/typeload.ml
index dbea83f3655..9527ff46c48 100644
--- a/src/typing/typeload.ml
+++ b/src/typing/typeload.ml
@@ -862,8 +862,13 @@ let init_core_api ctx c =
| _ ->
raise_typing_error ("Field " ^ f.cf_name ^ " has different property access than core type") p;
end;
- (match follow f.cf_type, follow f2.cf_type with
- | TFun (pl1,_), TFun (pl2,_) ->
+ (match follow_with_coro f.cf_type, follow_with_coro f2.cf_type with
+ | Coro _,NotCoro _ ->
+ raise_typing_error "Method should be coroutine" p
+ | NotCoro _,Coro _ ->
+ raise_typing_error "Method should not be coroutine" p;
+ | NotCoro (TFun (pl1,_)), NotCoro(TFun (pl2,_))
+ | Coro (pl1,_), Coro(pl2,_) ->
if List.length pl1 != List.length pl2 then raise_typing_error "Argument count mismatch" p;
List.iter2 (fun (n1,_,_) (n2,_,_) ->
if n1 <> n2 then raise_typing_error ("Method parameter name '" ^ n2 ^ "' should be '" ^ n1 ^ "'") p;
diff --git a/src/typing/typeloadCheck.ml b/src/typing/typeloadCheck.ml
index b814fc5a0c1..e03393fc1d0 100644
--- a/src/typing/typeloadCheck.ml
+++ b/src/typing/typeloadCheck.ml
@@ -64,8 +64,13 @@ let valid_redefinition map1 map2 f1 t1 f2 t2 = (* child, parent *)
end;
begin match f1.cf_kind,f2.cf_kind with
| Method m1, Method m2 when not (m1 = MethDynamic) && not (m2 = MethDynamic) ->
- begin match follow t1, follow t2 with
- | TFun (args1,r1) , TFun (args2,r2) ->
+ begin match follow_with_coro t1, follow_with_coro t2 with
+ | Coro _,NotCoro _ ->
+ raise (Unify_error [Unify_custom "Method should be coroutine"])
+ | NotCoro _,Coro _ ->
+ raise (Unify_error [Unify_custom "Method should not be coroutine"]);
+ | NotCoro (TFun (args1,r1)), NotCoro(TFun (args2,r2))
+ | Coro (args1,r1), Coro(args2,r2) ->
if not (List.length args1 = List.length args2) then raise (Unify_error [Unify_custom "Different number of function arguments"]);
let i = ref 0 in
begin try
diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml
index 26f63cac109..374e60edb17 100644
--- a/src/typing/typeloadFields.ml
+++ b/src/typing/typeloadFields.ml
@@ -869,7 +869,9 @@ module TypeBinding = struct
(match e.eexpr with
| TBlock [] | TBlock [{ eexpr = TConst _ }] | TConst _ | TObjectDecl [] -> ()
| _ -> TClass.set_cl_init c e);
- cf.cf_expr <- Some (mk (TFunction tf) t p);
+ let e = mk (TFunction tf) t p in
+ let e = if TyperManager.is_coroutine_context ctx && not (Meta.has Meta.CoroutineTransformed cf.cf_meta) then Coro.fun_to_coro (Coro.create_coro_context ctx cf.cf_meta) (ClassField(c, cf, tf, p)) else e in
+ cf.cf_expr <- Some e;
cf.cf_type <- t;
check_field_display ctx fctx c cf;
end;
@@ -1256,9 +1258,21 @@ let create_method (ctx,cctx,fctx) c f cf fd p =
ctx.type_params <- params @ ctx.type_params;
let args,ret = setup_args_ret ctx cctx fctx (fst f.cff_name) fd p in
- let function_mode = FunFunction in
+ let is_coroutine = Meta.has Meta.Coroutine f.cff_meta in
+ let function_mode = if is_coroutine then FunCoroutine else FunFunction in
let targs = args#for_type in
- let t = TFun (targs,ret) in
+ let t = if not is_coroutine then
+ TFun (targs,ret)
+ else if Meta.has Meta.CoroutineTransformed cf.cf_meta then begin
+ match List.rev targs with
+ | _ :: targs ->
+ (* Ignore trailing continuation for actual signature *)
+ ctx.t.tcoro.tcoro (List.rev targs) ret
+ | _ ->
+ die "" __LOC__
+ end else
+ ctx.t.tcoro.tcoro targs ret
+ in
cf.cf_type <- t;
cf.cf_kind <- Method (if fctx.is_macro then MethMacro else if fctx.is_inline then MethInline else if dynamic then MethDynamic else MethNormal);
cf.cf_params <- params;
diff --git a/src/typing/typer.ml b/src/typing/typer.ml
index 834e0250ea8..da239c83fd1 100644
--- a/src/typing/typer.ml
+++ b/src/typing/typer.ml
@@ -935,7 +935,7 @@ and type_new ctx ptp el with_type force_inline p =
| None ->
raise_typing_error_ext (make_error (No_constructor (TClassDecl c)) p)
| Some(tl,tr) ->
- let el,_ = unify_call_args ctx el tl tr p false false false in
+ let el = unify_call_args ctx el tl tr p false false false in
mk (TNew (c,params,el)) t p
end
| TAbstract({a_impl = Some c} as a,tl) when not (Meta.has Meta.MultiType a.a_meta) ->
@@ -1106,7 +1106,7 @@ and type_map_declaration ctx e1 el with_type p =
let el = (mk (TVar (v,Some enew)) t_dynamic p) :: (List.rev el) in
mk (TBlock el) tmap p
-and type_local_function ctx_from kind f with_type p =
+and type_local_function ctx_from kind f with_type want_coroutine p =
let name,inline = match kind with FKNamed (name,inline) -> Some name,inline | _ -> None,false in
let params = TypeloadFunction.type_function_params ctx_from f TPHLocal (match name with None -> "localfun" | Some (n,_) -> n) in
let curfun = match ctx_from.e.curfun with
@@ -1115,11 +1115,21 @@ and type_local_function ctx_from kind f with_type p =
| FunMemberAbstractLocal -> FunMemberAbstractLocal
| _ -> FunMemberClassLocal
in
- let function_mode = FunFunction in
+ let is_coroutine = match name, with_type with
+ | None, WithType.WithType (texpected,_) when not (ExtType.is_mono (follow texpected)) ->
+ (match follow_with_coro texpected with
+ | Coro _ ->
+ true
+ | _ ->
+ false)
+ | _ ->
+ want_coroutine
+ in
+ let function_mode = if is_coroutine then FunCoroutine else FunFunction in
let ctx = TyperManager.clone_for_expr ctx_from curfun function_mode in
let vname,pname= match name with
| None ->
- if params <> [] then begin
+ if params <> [] || is_coroutine then begin
Some(gen_local_prefix,VGenerated),null_pos
end else
None,p
@@ -1153,8 +1163,9 @@ and type_local_function ctx_from kind f with_type p =
let m = new unification_matrix (arity + 1) in
let rec loop l = match l with
| t :: l ->
- begin match follow t with
- | TFun(args,ret) when List.length args = arity ->
+ begin match follow_with_coro t with
+ | NotCoro(TFun(args,ret))
+ | Coro(args,ret) when List.length args = arity ->
List.iteri (fun i (_,_,t) ->
(* We don't want to bind monomorphs because we want the widest type *)
let t = dynamify_monos t in
@@ -1187,14 +1198,15 @@ and type_local_function ctx_from kind f with_type p =
(match with_type with
| WithType.WithType(t,_) ->
let rec loop stack t =
- (match follow t with
- | TFun (args2,tr) when List.length args2 = List.length targs ->
+ (match follow_with_coro t with
+ | NotCoro (TFun (args2,tr))
+ | Coro(args2,tr) when List.length args2 = List.length targs ->
List.iter2 (fun (_,_,t1) (_,_,t2) ->
maybe_unify_arg t1 t2
) targs args2;
(* unify for top-down inference unless we are expecting Void *)
maybe_unify_ret tr
- | TAbstract(a,tl) ->
+ | NotCoro (TAbstract(a,tl)) ->
begin match get_abstract_froms ctx a tl with
| [(_,t2)] ->
if not (List.exists (shallow_eq t) stack) then loop (t :: stack) t2
@@ -1221,8 +1233,9 @@ and type_local_function ctx_from kind f with_type p =
| WithType.NoValue ->
if name = None then display_error ctx.com "Unnamed lvalue functions are not supported" p
| _ ->
- ());
- let ft = TFun (targs,rt) in
+ ()
+ );
+ let ft = if is_coroutine then ctx.t.tcoro.tcoro targs rt else TFun(targs,rt) in
let ft = match with_type with
| WithType.NoValue ->
ft
@@ -1245,8 +1258,10 @@ and type_local_function ctx_from kind f with_type p =
tf_expr = e;
} in
let e = mk (TFunction tf) ft p in
+ let e = if TyperManager.is_coroutine_context ctx then Coro.fun_to_coro (Coro.create_coro_context ctx ctx.f.meta) (LocalFunc(tf,Option.get v)) else e in
match v with
- | None -> e
+ | None ->
+ e
| Some v ->
Typeload.generate_args_meta ctx.com None (fun m -> v.v_meta <- m :: v.v_meta) f.f_args;
let open LocalUsage in
@@ -1545,6 +1560,20 @@ and type_meta ?(mode=MGet) ctx m e1 with_type p =
| (EReturn e, p) -> type_return ~implicit:true ctx e with_type p
| _ -> e()
end
+ | (Meta.Coroutine,_,_) ->
+ let old = ctx.f.meta in
+ let rec loop e1 = match fst e1 with
+ | EMeta(m,e1) ->
+ ctx.f.meta <- m :: ctx.f.meta;
+ loop e1
+ | EFunction (kind, f) ->
+ type_local_function ctx kind f with_type true p
+ | _ ->
+ e ()
+ in
+ let e = loop e1 in
+ ctx.f.meta <- old;
+ e
(* Allow `${...}` reification because it's a noop and happens easily with macros *)
| (Meta.Dollar "",_,p) ->
e()
@@ -1892,7 +1921,7 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) =
| EUnop (op,flag,e) ->
type_unop ctx op flag e with_type p
| EFunction (kind,f) ->
- type_local_function ctx kind f with_type p
+ type_local_function ctx kind f with_type false p
| EUntyped e ->
let old = ctx.f.untyped in
ctx.f.untyped <- true;
diff --git a/src/typing/typerBase.ml b/src/typing/typerBase.ml
index e6cb480d21d..21209f50064 100644
--- a/src/typing/typerBase.ml
+++ b/src/typing/typerBase.ml
@@ -172,6 +172,16 @@ let get_this ctx p =
| FunMemberAbstract ->
let v = (try PMap.find "this" ctx.f.locals with Not_found -> raise_typing_error "Cannot reference this abstract here" p) in
mk (TLocal v) v.v_type p
+ | FunConstructor | FunMember when TyperManager.is_coroutine_context ctx ->
+ let v = match ctx.f.vthis with
+ | None ->
+ let v = add_local ctx VGenerated (Printf.sprintf "%sthis" gen_local_prefix) ctx.c.tthis p in
+ ctx.f.vthis <- Some v;
+ v
+ | Some v ->
+ v
+ in
+ mk (TLocal v) ctx.c.tthis p
| FunConstructor | FunMember ->
mk (TConst TThis) ctx.c.tthis p
diff --git a/src/typing/typerEntry.ml b/src/typing/typerEntry.ml
index 9346e9ffe35..3c6e4695ae1 100644
--- a/src/typing/typerEntry.ml
+++ b/src/typing/typerEntry.ml
@@ -114,7 +114,8 @@ let load_unit ctx =
| TEnumDecl en ->
(match snd en.e_path with
| "Unit" ->
- ctx.m.import_resolution#add (module_type_resolution mt None null_pos);
+ ctx.t.tunit <- TEnum(en,[]);
+ (* ctx.m.import_resolution#add (module_type_resolution mt None null_pos); *)
| _ -> ())
| _ -> ()
) m.m_types
@@ -135,6 +136,61 @@ let load_enum_tools ctx =
| _ ->
die "" __LOC__
+let load_coro ctx =
+ let m = TypeloadModule.load_module ctx (["haxe";"coro"],"Coroutine") null_pos in
+ List.iter (function
+ | TAbstractDecl({a_path = (["haxe";"coro"],"Coroutine")} as a) ->
+ let mk_coro args ret =
+ TAbstract(a,[TFun(args,ret)])
+ in
+ ctx.t.tcoro.tcoro <- mk_coro
+ | _ ->
+ ()
+ ) m.m_types;
+ let m = TypeloadModule.load_module ctx (["haxe";"coro"],"IContinuation") null_pos in
+ List.iter (function
+ | TClassDecl({ cl_path = (["haxe";"coro"], "IContinuation") } as cl) ->
+ ctx.t.tcoro.continuation <- TInst(cl, [ ctx.t.tany ]);
+ | _ ->
+ ()
+ ) m.m_types;
+ let m = TypeloadModule.load_module ctx (["haxe";"coro"],"BaseContinuation") null_pos in
+ List.iter (function
+ | TClassDecl({ cl_path = (["haxe";"coro"], "BaseContinuation") } as cl) ->
+ ctx.t.tcoro.base_continuation_class <- cl;
+ | _ ->
+ ()
+ ) m.m_types;
+ let m = TypeloadModule.load_module ctx (["haxe";"coro"],"SuspensionResult") null_pos in
+ List.iter (function
+ | TClassDecl({ cl_path = (["haxe";"coro"], "SuspensionResult") } as cl) ->
+ ctx.t.tcoro.suspension_result <- (fun t -> TInst(cl, [t]));
+ ctx.t.tcoro.suspension_result_class <- cl;
+ | _ ->
+ ()
+ ) m.m_types;
+ let m = TypeloadModule.load_module ctx (["haxe";"coro"],"ImmediateSuspensionResult") null_pos in
+ List.iter (function
+ | TClassDecl({ cl_path = (["haxe";"coro"], "ImmediateSuspensionResult") } as cl) ->
+ ctx.t.tcoro.immediate_suspension_result_class <- cl;
+ | _ ->
+ ()
+ ) m.m_types;
+ let m = TypeloadModule.load_module ctx (["haxe";"coro"],"SuspensionState") null_pos in
+ List.iter (function
+ | TAbstractDecl({a_path = (["haxe";"coro"],"SuspensionState")} as a) ->
+ ctx.t.tcoro.suspension_state <- TAbstract(a,[])
+ | _ ->
+ ()
+ ) m.m_types;
+ let m = TypeloadModule.load_module ctx (["haxe"],"Exception") null_pos in
+ List.iter (function
+ | TClassDecl({ cl_path = (["haxe"], "Exception") } as cl) ->
+ ctx.t.texception <- TInst(cl, [])
+ | _ ->
+ ()
+ ) m.m_types
+
let create com macros =
let rec ctx = {
com = com;
@@ -154,6 +210,7 @@ let create com macros =
return_partial_type = false;
build_count = 0;
t_dynamic_def = t_dynamic;
+ continuation_api = None;
do_macro = MacroContext.type_macro;
do_load_macro = MacroContext.load_macro';
do_load_module = TypeloadModule.load_module;
@@ -190,9 +247,10 @@ let create com macros =
load_string ctx;
load_std ctx;
load_any ctx;
- (* load_unit ctx; *)
+ load_unit ctx;
load_array ctx;
load_enum_tools ctx;
+ load_coro ctx;
ignore(TypeloadModule.load_module ctx (["haxe"],"Exception") null_pos);
ctx.g.complete <- true;
ctx
diff --git a/std/StdTypes.hx b/std/StdTypes.hx
index b67e3c45556..37e18ac524b 100644
--- a/std/StdTypes.hx
+++ b/std/StdTypes.hx
@@ -20,7 +20,6 @@
* DEALINGS IN THE SOFTWARE.
*/
// standard Haxe types
-
/**
The standard `Void` type. Only `null` values can be of the type `Void`.
@@ -169,4 +168,4 @@ typedef KeyValueIterable = {
@see https://haxe.org/manual/types-abstract-array-access.html
**/
-extern interface ArrayAccess {}
+extern interface ArrayAccess {}
\ No newline at end of file
diff --git a/std/eval/_std/haxe/coro/EventLoopImpl.hx b/std/eval/_std/haxe/coro/EventLoopImpl.hx
new file mode 100644
index 00000000000..319e9ad0760
--- /dev/null
+++ b/std/eval/_std/haxe/coro/EventLoopImpl.hx
@@ -0,0 +1,221 @@
+package haxe.coro;
+
+import sys.thread.Mutex;
+
+/**
+ When an event loop has an available event to execute.
+**/
+private enum NextEventTime {
+ /** There's already an event waiting to be executed */
+ Now;
+ /** No new events are expected. */
+ Never;
+ /** An event is expected to be ready for execution at `time`. */
+ At(time:Float);
+}
+
+private class SimpleEventLoop {
+ final mutex = new Mutex();
+ final oneTimeEvents = new ArrayVoid>>();
+ var oneTimeEventsIdx = 0;
+ var regularEvents:Null;
+
+ public function new():Void {}
+
+ /**
+ Schedule event for execution every `intervalMs` milliseconds in current loop.
+ **/
+ public function repeat(event:()->Void, intervalMs:Int):EventHandler {
+ var interval = 0.001 * intervalMs;
+ var event = new RegularEvent(event, haxe.Timer.stamp() + interval, interval);
+ mutex.acquire();
+ insertEventByTime(event);
+ mutex.release();
+ return event;
+ }
+
+ function insertEventByTime(event:RegularEvent):Void {
+ switch regularEvents {
+ case null:
+ regularEvents = event;
+ case current:
+ var previous = null;
+ while(true) {
+ if(current == null) {
+ previous.next = event;
+ event.previous = previous;
+ break;
+ } else if(event.nextRunTime < current.nextRunTime) {
+ event.next = current;
+ current.previous = event;
+ switch previous {
+ case null:
+ regularEvents = event;
+ case _:
+ event.previous = previous;
+ previous.next = event;
+ current.previous = event;
+ }
+ break;
+ } else {
+ previous = current;
+ current = current.next;
+ }
+ }
+ }
+ }
+
+ /**
+ Prevent execution of a previously scheduled event in current loop.
+ **/
+ public function cancel(eventHandler:EventHandler):Void {
+ var event:RegularEvent = eventHandler;
+ mutex.acquire();
+ event.cancelled = true;
+ if(regularEvents == event) {
+ regularEvents = event.next;
+ }
+ switch event.next {
+ case null:
+ case e: e.previous = event.previous;
+ }
+ switch event.previous {
+ case null:
+ case e: e.next = event.next;
+ }
+ event.next = event.previous = null;
+ mutex.release();
+ }
+
+ /**
+ Execute `event` as soon as possible.
+ **/
+ public function run(event:()->Void):Void {
+ mutex.acquire();
+ oneTimeEvents[oneTimeEventsIdx++] = event;
+ mutex.release();
+ }
+
+ /**
+ Executes all pending events.
+
+ The returned time stamps can be used with `Sys.time()` for calculations.
+
+ Depending on a target platform this method may be non-reentrant. It must
+ not be called from event callbacks.
+ **/
+ public function progress():NextEventTime {
+ return switch __progress(haxe.Timer.stamp(), [], []) {
+ case -2: Now;
+ case -1: Never;
+ case time: At(time);
+ }
+ }
+
+ /**
+ Execute all pending events.
+ Wait and execute as many events as the number of times `promise()` was called.
+ Runs until all repeating events are cancelled and no more events are expected.
+
+ Depending on a target platform this method may be non-reentrant. It must
+ not be called from event callbacks.
+ **/
+ public function loop():Void {
+ var recycleRegular = [];
+ var recycleOneTimers = [];
+ while(true) {
+ var r = __progress(haxe.Timer.stamp(), recycleRegular, recycleOneTimers);
+ switch r {
+ case -1:
+ case -2:
+ break;
+ case time:
+ var timeout = time - haxe.Timer.stamp();
+ }
+ }
+ }
+
+ /**
+ `.progress` implementation with a reusable array for internal usage.
+ The `nextEventAt` field of the return value denotes when the next event
+ is expected to run:
+ * -1 - never
+ * -2 - now
+ * other values - at specified time
+ **/
+ inline function __progress(now:Float, recycleRegular:Array, recycleOneTimers:Array<()->Void>):Float {
+ var regularsToRun = recycleRegular;
+ var eventsToRunIdx = 0;
+ // When the next event is expected to run
+ var nextEventAt:Float = -1;
+
+ mutex.acquire();
+ // Collect regular events to run
+ var current = regularEvents;
+ while(current != null) {
+ if(current.nextRunTime <= now) {
+ regularsToRun[eventsToRunIdx++] = current;
+ current.nextRunTime += current.interval;
+ nextEventAt = -2;
+ } else if(nextEventAt == -1 || current.nextRunTime < nextEventAt) {
+ nextEventAt = current.nextRunTime;
+ }
+ current = current.next;
+ }
+ mutex.release();
+
+ // Run regular events
+ for(i in 0...eventsToRunIdx) {
+ if(!regularsToRun[i].cancelled)
+ regularsToRun[i].run();
+ regularsToRun[i] = null;
+ }
+ eventsToRunIdx = 0;
+
+ var oneTimersToRun = recycleOneTimers;
+ mutex.acquire();
+ // Collect pending one-time events
+ for(i => event in oneTimeEvents) {
+ switch event {
+ case null:
+ break;
+ case _:
+ oneTimersToRun[eventsToRunIdx++] = event;
+ oneTimeEvents[i] = null;
+ }
+ }
+ oneTimeEventsIdx = 0;
+ mutex.release();
+
+ //run events
+ for(i in 0...eventsToRunIdx) {
+ oneTimersToRun[i]();
+ oneTimersToRun[i] = null;
+ }
+
+ // Some events were executed. They could add new events to run.
+ if(eventsToRunIdx > 0) {
+ nextEventAt = -2;
+ }
+ return nextEventAt;
+ }
+}
+
+abstract EventHandler(RegularEvent) from RegularEvent to RegularEvent {}
+
+private class RegularEvent {
+ public var nextRunTime:Float;
+ public final interval:Float;
+ public final run:()->Void;
+ public var next:Null;
+ public var previous:Null;
+ public var cancelled:Bool = false;
+
+ public function new(run:()->Void, nextRunTime:Float, interval:Float) {
+ this.run = run;
+ this.nextRunTime = nextRunTime;
+ this.interval = interval;
+ }
+}
+
+typedef EventLoopImpl = SimpleEventLoop;
\ No newline at end of file
diff --git a/std/haxe/coro/BaseContinuation.hx b/std/haxe/coro/BaseContinuation.hx
new file mode 100644
index 00000000000..476f07e73f7
--- /dev/null
+++ b/std/haxe/coro/BaseContinuation.hx
@@ -0,0 +1,106 @@
+package haxe.coro;
+
+import haxe.coro.context.Context;
+import haxe.coro.schedulers.Scheduler;
+import haxe.CallStack.StackItem;
+import haxe.Exception;
+
+abstract class BaseContinuation extends SuspensionResult implements IContinuation implements IStackFrame {
+ public final completion:IContinuation;
+
+ public final context:Context;
+
+ public var gotoLabel:Int;
+
+ public var recursing:Bool;
+
+ function new(completion:IContinuation, initialLabel:Int) {
+ this.completion = completion;
+
+ context = completion.context;
+ gotoLabel = initialLabel;
+ error = null;
+ result = null;
+ recursing = false;
+ }
+
+ public final function resume(result:Any, error:Exception):Void {
+ this.result = result;
+ this.error = error;
+ context.get(Scheduler.key).schedule(() -> {
+ recursing = false;
+
+ #if coroutine.throw
+ try {
+ #end
+ final result = invokeResume();
+ switch (result.state) {
+ case Pending:
+ return;
+ case Returned:
+ completion.resume(result.result, null);
+ case Thrown:
+ completion.resume(result.result, result.error);
+ }
+ #if coroutine.throw
+ } catch (e:Dynamic) {
+ completion.resume(null, @:privateAccess Exception.thrown(e));
+ }
+ #end
+ });
+ }
+
+ public function callerFrame():Null {
+ return if (completion is IStackFrame) {
+ cast completion;
+ } else {
+ null;
+ }
+ }
+
+ public function getStackItem():Null {
+ return cast result;
+ }
+
+ public function setClassFuncStackItem(cls:String, func:String, file:String, line:Int, pos:Int, pmin:Int, pmax:Int) {
+ result = cast StackItem.FilePos(StackItem.Method(cls, func), file, line, pos);
+ #if eval
+ eval.vm.Context.callMacroApi("associate_enum_value_pos")(result, haxe.macro.Context.makePosition({file: file, min: pmin, max: pmax}));
+ #end
+ }
+
+ public function setLocalFuncStackItem(id:Int, file:String, line:Int, pos:Int, pmin:Int, pmax:Int) {
+ result = cast StackItem.FilePos(StackItem.LocalFunction(id), file, line, pos);
+ #if eval
+ eval.vm.Context.callMacroApi("associate_enum_value_pos")(result, haxe.macro.Context.makePosition({file: file, min: pmin, max: pmax}));
+ #end
+ }
+
+ public function startException(fromThrow:Bool) {
+ if (fromThrow) {
+ /*
+ This comes from a coro-level throw, which pushes its position via one of the functions
+ above. In this case we turn result into the stack item array now.
+ */
+ result = cast [result];
+ } else {
+ /*
+ This means we caught an exception, which must come from outside our current coro. We
+ don't need our current result value because if anything it points to the last
+ suspension call.
+ */
+ result = cast [];
+ }
+ }
+
+ public function buildCallStack() {
+ var frame = callerFrame();
+ if (frame != null) {
+ var result:Array = cast result;
+ result ??= [];
+ result.push(frame.getStackItem());
+ }
+ }
+
+ abstract function invokeResume():SuspensionResult;
+}
\ No newline at end of file
diff --git a/std/haxe/coro/Coroutine.hx b/std/haxe/coro/Coroutine.hx
new file mode 100644
index 00000000000..bd191ab9116
--- /dev/null
+++ b/std/haxe/coro/Coroutine.hx
@@ -0,0 +1,60 @@
+package haxe.coro;
+
+import haxe.coro.EventLoop;
+import haxe.coro.schedulers.EventLoopScheduler;
+import haxe.coro.schedulers.Scheduler;
+import haxe.coro.continuations.RacingContinuation;
+import haxe.coro.continuations.BlockingContinuation;
+
+private class CoroSuspend extends haxe.coro.BaseContinuation {
+ public function new(completion:haxe.coro.IContinuation) {
+ super(completion, 1);
+ }
+
+ public function invokeResume():SuspensionResult {
+ return Coroutine.suspend(null, this);
+ }
+}
+
+/**
+ Coroutine function.
+**/
+@:callable
+@:coreType
+abstract Coroutine {
+ @:coroutine @:coroutine.transformed
+ public static function suspend(func:haxe.coro.IContinuation->Void, completion:haxe.coro.IContinuation):T {
+ var continuation = new CoroSuspend(completion);
+ var safe = new haxe.coro.continuations.RacingContinuation(completion, continuation);
+ func(safe);
+ safe.resolve();
+ return cast continuation;
+ }
+
+ @:coroutine @:coroutine.nothrow public static function delay(ms:Int):Void {
+ Coroutine.suspend(cont -> {
+ cont.context.get(Scheduler.key).scheduleIn(() -> cont.resume(null, null), ms);
+ });
+ }
+
+ @:coroutine @:coroutine.nothrow public static function yield():Void {
+ Coroutine.suspend(cont -> {
+ cont.context.get(Scheduler.key).schedule(() -> cont.resume(null, null));
+ });
+ }
+
+ public static function run(f:Coroutine<() -> T>):T {
+ final loop = new EventLoop();
+ final cont = new BlockingContinuation(loop, new EventLoopScheduler(loop));
+ final result = f(cont);
+
+ return switch (result.state) {
+ case Pending:
+ cont.wait();
+ case Returned:
+ result.result;
+ case Thrown:
+ throw result.error;
+ }
+ }
+}
diff --git a/std/haxe/coro/EventLoop.hx b/std/haxe/coro/EventLoop.hx
new file mode 100644
index 00000000000..7f796421238
--- /dev/null
+++ b/std/haxe/coro/EventLoop.hx
@@ -0,0 +1,38 @@
+package haxe.coro;
+
+#if (target.threaded && !cppia && !eval)
+import sys.thread.EventLoop;
+private typedef EventLoopImpl = sys.thread.EventLoop;
+#else
+import haxe.coro.EventLoopImpl;
+private typedef EventLoopImpl = haxe.coro.EventLoopImpl;
+#end
+
+@:coreApi abstract EventLoop(EventLoopImpl) {
+ public function new() {
+ this = new EventLoopImpl();
+ }
+
+ public function tick():Bool {
+ return switch this.progress() {
+ case Never:
+ false;
+ case _:
+ true;
+ }
+ }
+
+ public function run(func:()->Void):Void {
+ this.run(func);
+ }
+
+ public function runIn(func:()->Void, ms:Int):Void {
+ var handle : EventHandler = null;
+
+ handle = this.repeat(() -> {
+ this.cancel(handle);
+
+ func();
+ }, ms);
+ }
+}
\ No newline at end of file
diff --git a/std/haxe/coro/EventLoopImpl.hx b/std/haxe/coro/EventLoopImpl.hx
new file mode 100644
index 00000000000..5929f955d6b
--- /dev/null
+++ b/std/haxe/coro/EventLoopImpl.hx
@@ -0,0 +1,208 @@
+package haxe.coro;
+
+/**
+ When an event loop has an available event to execute.
+**/
+private enum NextEventTime {
+ /** There's already an event waiting to be executed */
+ Now;
+ /** No new events are expected. */
+ Never;
+ /** An event is expected to be ready for execution at `time`. */
+ At(time:Float);
+}
+
+private class SimpleEventLoop {
+ final oneTimeEvents = new ArrayVoid>>();
+ var oneTimeEventsIdx = 0;
+ var regularEvents:Null;
+
+ public function new():Void {}
+
+ /**
+ Schedule event for execution every `intervalMs` milliseconds in current loop.
+ **/
+ public function repeat(event:()->Void, intervalMs:Int):EventHandler {
+ var interval = 0.001 * intervalMs;
+ var event = new RegularEvent(event, haxe.Timer.stamp() + interval, interval);
+ insertEventByTime(event);
+ return event;
+ }
+
+ function insertEventByTime(event:RegularEvent):Void {
+ switch regularEvents {
+ case null:
+ regularEvents = event;
+ case current:
+ var previous = null;
+ while(true) {
+ if(current == null) {
+ previous.next = event;
+ event.previous = previous;
+ break;
+ } else if(event.nextRunTime < current.nextRunTime) {
+ event.next = current;
+ current.previous = event;
+ switch previous {
+ case null:
+ regularEvents = event;
+ case _:
+ event.previous = previous;
+ previous.next = event;
+ current.previous = event;
+ }
+ break;
+ } else {
+ previous = current;
+ current = current.next;
+ }
+ }
+ }
+ }
+
+ /**
+ Prevent execution of a previously scheduled event in current loop.
+ **/
+ public function cancel(eventHandler:EventHandler):Void {
+ var event:RegularEvent = eventHandler;
+ event.cancelled = true;
+ if(regularEvents == event) {
+ regularEvents = event.next;
+ }
+ switch event.next {
+ case null:
+ case e: e.previous = event.previous;
+ }
+ switch event.previous {
+ case null:
+ case e: e.next = event.next;
+ }
+ event.next = event.previous = null;
+ }
+
+ /**
+ Execute `event` as soon as possible.
+ **/
+ public function run(event:()->Void):Void {
+ oneTimeEvents[oneTimeEventsIdx++] = event;
+ }
+
+ /**
+ Executes all pending events.
+
+ The returned time stamps can be used with `Sys.time()` for calculations.
+
+ Depending on a target platform this method may be non-reentrant. It must
+ not be called from event callbacks.
+ **/
+ public function progress():NextEventTime {
+ return switch __progress(haxe.Timer.stamp(), [], []) {
+ case -2: Now;
+ case -1: Never;
+ case time: At(time);
+ }
+ }
+
+ /**
+ Execute all pending events.
+ Wait and execute as many events as the number of times `promise()` was called.
+ Runs until all repeating events are cancelled and no more events are expected.
+
+ Depending on a target platform this method may be non-reentrant. It must
+ not be called from event callbacks.
+ **/
+ public function loop():Void {
+ var recycleRegular = [];
+ var recycleOneTimers = [];
+ while(true) {
+ var r = __progress(haxe.Timer.stamp(), recycleRegular, recycleOneTimers);
+ switch r {
+ case -1:
+ case -2:
+ break;
+ case time:
+ var timeout = time - haxe.Timer.stamp();
+ }
+ }
+ }
+
+ /**
+ `.progress` implementation with a reusable array for internal usage.
+ The `nextEventAt` field of the return value denotes when the next event
+ is expected to run:
+ * -1 - never
+ * -2 - now
+ * other values - at specified time
+ **/
+ inline function __progress(now:Float, recycleRegular:Array, recycleOneTimers:Array<()->Void>):Float {
+ var regularsToRun = recycleRegular;
+ var eventsToRunIdx = 0;
+ // When the next event is expected to run
+ var nextEventAt:Float = -1;
+
+ // Collect regular events to run
+ var current = regularEvents;
+ while(current != null) {
+ if(current.nextRunTime <= now) {
+ regularsToRun[eventsToRunIdx++] = current;
+ current.nextRunTime += current.interval;
+ nextEventAt = -2;
+ } else if(nextEventAt == -1 || current.nextRunTime < nextEventAt) {
+ nextEventAt = current.nextRunTime;
+ }
+ current = current.next;
+ }
+
+ // Run regular events
+ for(i in 0...eventsToRunIdx) {
+ if(!regularsToRun[i].cancelled)
+ regularsToRun[i].run();
+ regularsToRun[i] = null;
+ }
+ eventsToRunIdx = 0;
+
+ var oneTimersToRun = recycleOneTimers;
+ // Collect pending one-time events
+ for(i => event in oneTimeEvents) {
+ switch event {
+ case null:
+ break;
+ case _:
+ oneTimersToRun[eventsToRunIdx++] = event;
+ oneTimeEvents[i] = null;
+ }
+ }
+ oneTimeEventsIdx = 0;
+
+ //run events
+ for(i in 0...eventsToRunIdx) {
+ oneTimersToRun[i]();
+ oneTimersToRun[i] = null;
+ }
+
+ // Some events were executed. They could add new events to run.
+ if(eventsToRunIdx > 0) {
+ nextEventAt = -2;
+ }
+ return nextEventAt;
+ }
+}
+
+abstract EventHandler(RegularEvent) from RegularEvent to RegularEvent {}
+
+private class RegularEvent {
+ public var nextRunTime:Float;
+ public final interval:Float;
+ public final run:()->Void;
+ public var next:Null;
+ public var previous:Null;
+ public var cancelled:Bool = false;
+
+ public function new(run:()->Void, nextRunTime:Float, interval:Float) {
+ this.run = run;
+ this.nextRunTime = nextRunTime;
+ this.interval = interval;
+ }
+}
+
+typedef EventLoopImpl = SimpleEventLoop;
\ No newline at end of file
diff --git a/std/haxe/coro/IContinuation.hx b/std/haxe/coro/IContinuation.hx
new file mode 100644
index 00000000000..da2b8517f10
--- /dev/null
+++ b/std/haxe/coro/IContinuation.hx
@@ -0,0 +1,10 @@
+package haxe.coro;
+
+import haxe.Exception;
+import haxe.coro.context.Context;
+
+interface IContinuation {
+ final context:Context;
+
+ function resume(result:T, error:Exception):Void;
+}
diff --git a/std/haxe/coro/IStackFrame.hx b/std/haxe/coro/IStackFrame.hx
new file mode 100644
index 00000000000..8bbd0c1ac1c
--- /dev/null
+++ b/std/haxe/coro/IStackFrame.hx
@@ -0,0 +1,8 @@
+package haxe.coro;
+
+import haxe.CallStack.StackItem;
+
+interface IStackFrame {
+ function getStackItem():Null;
+ function callerFrame():Null;
+}
\ No newline at end of file
diff --git a/std/haxe/coro/ImmediateSuspensionResult.hx b/std/haxe/coro/ImmediateSuspensionResult.hx
new file mode 100644
index 00000000000..ccd471a293e
--- /dev/null
+++ b/std/haxe/coro/ImmediateSuspensionResult.hx
@@ -0,0 +1,23 @@
+package haxe.coro;
+
+import haxe.Exception;
+
+class ImmediateSuspensionResult extends SuspensionResult {
+ function new(result:T, error:Exception) {
+ this.result = result;
+ this.error = error;
+ this.state = error == null ? Returned : Thrown;
+ }
+
+ static public function withResult(result:T) {
+ return new ImmediateSuspensionResult(result, null);
+ }
+
+ static public function withError(error:T) {
+ return new ImmediateSuspensionResult(cast [] /* stack items */, @:privateAccess haxe.Exception.thrown(error));
+ }
+
+ public override function toString() {
+ return '[ImmediateSuspensionResult ${state.toString()}, $result]';
+ }
+}
\ No newline at end of file
diff --git a/std/haxe/coro/Mutex.hx b/std/haxe/coro/Mutex.hx
new file mode 100644
index 00000000000..0916454feae
--- /dev/null
+++ b/std/haxe/coro/Mutex.hx
@@ -0,0 +1,45 @@
+package haxe.coro;
+
+#if (target.threaded)
+typedef Mutex = sys.thread.Mutex;
+#else
+typedef Mutex = StubMutex;
+
+/**
+ This is a stub version.
+ Creates a mutex, which can be used to acquire a temporary lock
+ to access some resource. The main difference with a lock is
+ that a mutex must always be released by the owner thread.
+**/
+class StubMutex {
+ /**
+ Creates a stub mutex on non threaded target.
+ **/
+ public function new():Void {}
+
+ /**
+ This is a stub version.
+ The current thread acquire the mutex or wait if not available.
+ The same thread can acquire several times the same mutex but
+ must release it as many times it has been acquired.
+ **/
+ public function acquire():Void {}
+
+ /**
+ This is a stub version.
+ Try to acquire the mutex, returns true if acquire or false
+ if it's already locked by another thread.
+ **/
+ public function tryAcquire():Bool {
+ return true;
+ }
+
+ /**
+ This is a stub version.
+ Release a mutex that has been acquired by the current thread.
+ The behavior is undefined if the current thread does not own
+ the mutex.
+ **/
+ public function release():Void {}
+}
+#end
diff --git a/std/haxe/coro/SuspensionResult.hx b/std/haxe/coro/SuspensionResult.hx
new file mode 100644
index 00000000000..d141e75226e
--- /dev/null
+++ b/std/haxe/coro/SuspensionResult.hx
@@ -0,0 +1,13 @@
+package haxe.coro;
+
+import haxe.Exception;
+
+abstract class SuspensionResult {
+ public var state:SuspensionState;
+ public var result:T;
+ public var error:Exception;
+
+ public function toString() {
+ return '[SuspensionResult ${state.toString()}, $result]';
+ }
+}
\ No newline at end of file
diff --git a/std/haxe/coro/SuspensionState.hx b/std/haxe/coro/SuspensionState.hx
new file mode 100644
index 00000000000..e9a88452783
--- /dev/null
+++ b/std/haxe/coro/SuspensionState.hx
@@ -0,0 +1,18 @@
+package haxe.coro;
+
+@:using(SuspensionState.SuspensionStateTools)
+enum abstract SuspensionState(Int) {
+ final Pending;
+ final Returned;
+ final Thrown;
+}
+
+class SuspensionStateTools {
+ static public function toString(c:SuspensionState) {
+ return switch (c) {
+ case Pending: "Pending";
+ case Returned: "Returned";
+ case Thrown: "Thrown";
+ }
+ }
+}
\ No newline at end of file
diff --git a/std/haxe/coro/context/Context.hx b/std/haxe/coro/context/Context.hx
new file mode 100644
index 00000000000..368afe4fc28
--- /dev/null
+++ b/std/haxe/coro/context/Context.hx
@@ -0,0 +1,59 @@
+package haxe.coro.context;
+
+import haxe.ds.BalancedTree;
+
+class ElementTree extends BalancedTree, IElement> {
+ override function compare(k1:Key, k2:Key) {
+ return k2.id - k1.id;
+ }
+
+ override function copy():ElementTree {
+ var copied = new ElementTree();
+ copied.root = root;
+ return copied;
+ }
+
+ override function toString() {
+ var buf = new StringBuf();
+ var first = true;
+ for (key => value in this) {
+ if (!first) {
+ buf.add(", ");
+ } else {
+ first = false;
+ }
+ buf.add('${key.name}: $value');
+ }
+ return buf.toString();
+ }
+}
+
+abstract Context(ElementTree) {
+ public function new(tree:ElementTree) {
+ this = tree;
+ }
+
+ public function add>(value:T) {
+ this.set(value.getKey(), value);
+ }
+
+ public function clone():Context {
+ return new Context(this.copy());
+ }
+
+ public function set, V>(key:Key, value:T):Void {
+ this.set(key, value);
+ }
+
+ public function get(key:Key):T {
+ return cast this.get(key);
+ }
+
+ public function toString() {
+ return this.toString();
+ }
+
+ static public function empty() {
+ return new Context(new ElementTree());
+ }
+}
diff --git a/std/haxe/coro/context/IElement.hx b/std/haxe/coro/context/IElement.hx
new file mode 100644
index 00000000000..e082ab68ac3
--- /dev/null
+++ b/std/haxe/coro/context/IElement.hx
@@ -0,0 +1,5 @@
+package haxe.coro.context;
+
+interface IElement {
+ public function getKey():Key;
+}
diff --git a/std/haxe/coro/context/Key.hx b/std/haxe/coro/context/Key.hx
new file mode 100644
index 00000000000..0410ee88a04
--- /dev/null
+++ b/std/haxe/coro/context/Key.hx
@@ -0,0 +1,21 @@
+package haxe.coro.context;
+
+class Key {
+ static var counter = 0;
+ static var counterMutex = new Mutex();
+
+ public final name:String;
+ public final id:Int;
+
+ function new(id:Int, name:String) {
+ this.name = name;
+ this.id = id;
+ }
+
+ static public function createNew(name:String) {
+ counterMutex.acquire();
+ var id = counter++;
+ counterMutex.release();
+ return new Key(id, name);
+ }
+}
\ No newline at end of file
diff --git a/std/haxe/coro/continuations/BlockingContinuation.hx b/std/haxe/coro/continuations/BlockingContinuation.hx
new file mode 100644
index 00000000000..6bf23632ef8
--- /dev/null
+++ b/std/haxe/coro/continuations/BlockingContinuation.hx
@@ -0,0 +1,60 @@
+package haxe.coro.continuations;
+
+import haxe.CallStack;
+import haxe.coro.context.Context;
+import haxe.coro.schedulers.Scheduler;
+
+class BlockingContinuation implements IContinuation {
+ public final context:Context;
+
+ final loop:EventLoop;
+
+ var running:Bool;
+ var result:T;
+ var error:Exception;
+
+ public function new(loop:EventLoop, scheduler:Scheduler) {
+ this.loop = loop;
+
+ context = Context.empty();
+ context.set(Scheduler.key, scheduler);
+ running = true;
+ error = null;
+ }
+
+ public function resume(result:T, error:Exception) {
+ running = false;
+
+ this.result = result;
+ this.error = error;
+ }
+
+ public function wait():T {
+ while (loop.tick() || running) {
+ // Busy wait
+ }
+
+ if (error != null) {
+ // trace((cast result : haxe.CallStack));
+ final topStack = [];
+ for (item in error.stack.asArray()) {
+ switch (item) {
+ // TODO: this needs a better check
+ case FilePos(_, _, -1, _):
+ break;
+ // this is a hack
+ case FilePos(Method(_, "invokeResume"), _):
+ break;
+ case _:
+ topStack.push(item);
+ }
+ }
+ final coroStack = (cast result : Array) ?? [];
+ final bottomStack = CallStack.callStack();
+ error.stack = topStack.concat(coroStack).concat(bottomStack);
+ throw error;
+ } else {
+ return result;
+ }
+ }
+}
diff --git a/std/haxe/coro/continuations/RacingContinuation.hx b/std/haxe/coro/continuations/RacingContinuation.hx
new file mode 100644
index 00000000000..496832fb5d2
--- /dev/null
+++ b/std/haxe/coro/continuations/RacingContinuation.hx
@@ -0,0 +1,85 @@
+package haxe.coro.continuations;
+
+import haxe.coro.context.Context;
+import haxe.coro.schedulers.Scheduler;
+
+#if (target.threaded && !cppia)
+import sys.thread.Lock;
+import sys.thread.Mutex;
+import sys.thread.Thread;
+#else
+private class Lock {
+ public function new() {}
+
+ public inline function release() {}
+
+ public inline function wait(?t:Float) {}
+}
+
+private class Mutex {
+ public function new() {}
+
+ public inline function acquire() {}
+
+ public inline function release() {}
+}
+
+private class Thread {
+ public static function create(f:Void->Void) {
+ f();
+ }
+}
+#end
+
+@:coreApi class RacingContinuation implements IContinuation {
+ final inputCont:IContinuation;
+ final outputCont:SuspensionResult;
+
+ final lock:Mutex;
+
+ var assigned:Bool;
+
+ public final context:Context;
+
+ public function new(inputCont:IContinuation, outputCont:SuspensionResult) {
+ this.inputCont = inputCont;
+ this.outputCont = outputCont;
+ context = inputCont.context;
+ assigned = false;
+ lock = new Mutex();
+ }
+
+ public function resume(result:T, error:Exception):Void {
+ context.get(Scheduler.key).schedule(() -> {
+ lock.acquire();
+
+ if (assigned) {
+ lock.release();
+ inputCont.resume(result, error);
+ } else {
+ assigned = true;
+ outputCont.result = result;
+ outputCont.error = error;
+
+ lock.release();
+ }
+ });
+ }
+
+ public function resolve():Void {
+ lock.acquire();
+ if (assigned) {
+ if (outputCont.error != null) {
+ outputCont.state = Thrown;
+ lock.release();
+ } else {
+ outputCont.state = Returned;
+ lock.release();
+ }
+ } else {
+ assigned = true;
+ outputCont.state = Pending;
+ lock.release();
+ }
+ }
+}
diff --git a/std/haxe/coro/schedulers/EventLoopScheduler.hx b/std/haxe/coro/schedulers/EventLoopScheduler.hx
new file mode 100644
index 00000000000..6db4dfdb50c
--- /dev/null
+++ b/std/haxe/coro/schedulers/EventLoopScheduler.hx
@@ -0,0 +1,25 @@
+package haxe.coro.schedulers;
+
+import haxe.coro.EventLoop;
+
+class EventLoopScheduler extends Scheduler {
+
+ final loop : EventLoop;
+
+ public function new(loop:EventLoop) {
+ super();
+ this.loop = loop;
+ }
+
+ public function schedule(func : ()->Void) {
+ loop.run(func);
+ }
+
+ public function scheduleIn(func : ()->Void, ms:Int) {
+ loop.runIn(func, ms);
+ }
+
+ public function toString() {
+ return '[EventLoopScheduler: $loop]';
+ }
+}
\ No newline at end of file
diff --git a/std/haxe/coro/schedulers/Scheduler.hx b/std/haxe/coro/schedulers/Scheduler.hx
new file mode 100644
index 00000000000..93b47536032
--- /dev/null
+++ b/std/haxe/coro/schedulers/Scheduler.hx
@@ -0,0 +1,18 @@
+package haxe.coro.schedulers;
+
+import haxe.coro.context.Key;
+import haxe.coro.context.IElement;
+
+abstract class Scheduler implements IElement {
+ public static final key:Key = Key.createNew('Scheduler');
+
+ function new() {}
+
+ public abstract function schedule(func:() -> Void):Void;
+
+ public abstract function scheduleIn(func:() -> Void, ms:Int):Void;
+
+ public function getKey() {
+ return key;
+ }
+}
diff --git a/tests/misc/coroutines/.gitignore b/tests/misc/coroutines/.gitignore
new file mode 100644
index 00000000000..444f0793565
--- /dev/null
+++ b/tests/misc/coroutines/.gitignore
@@ -0,0 +1,2 @@
+/test.js
+/test.js.map
diff --git a/tests/misc/coroutines/build-base.hxml b/tests/misc/coroutines/build-base.hxml
new file mode 100644
index 00000000000..0480a254d98
--- /dev/null
+++ b/tests/misc/coroutines/build-base.hxml
@@ -0,0 +1,5 @@
+--class-path src
+--library utest
+--main Main
+--debug
+-D UTEST-PRINT-TESTS
diff --git a/tests/misc/coroutines/build-cpp.hxml b/tests/misc/coroutines/build-cpp.hxml
new file mode 100644
index 00000000000..3244b4108d1
--- /dev/null
+++ b/tests/misc/coroutines/build-cpp.hxml
@@ -0,0 +1,2 @@
+build-base.hxml
+--cpp bin/cpp
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-eval.hxml b/tests/misc/coroutines/build-eval.hxml
new file mode 100644
index 00000000000..9b9dbcee78f
--- /dev/null
+++ b/tests/misc/coroutines/build-eval.hxml
@@ -0,0 +1,2 @@
+build-base.hxml
+--interp
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-hl.hxml b/tests/misc/coroutines/build-hl.hxml
new file mode 100644
index 00000000000..917f0cb77e5
--- /dev/null
+++ b/tests/misc/coroutines/build-hl.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--hl bin/coro.hl
+--cmd hl bin/coro.hl
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-js.hxml b/tests/misc/coroutines/build-js.hxml
new file mode 100644
index 00000000000..b68988c4f6a
--- /dev/null
+++ b/tests/misc/coroutines/build-js.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--js bin/coro.js
+--cmd node bin/coro.js
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-jvm.hxml b/tests/misc/coroutines/build-jvm.hxml
new file mode 100644
index 00000000000..359b874b931
--- /dev/null
+++ b/tests/misc/coroutines/build-jvm.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--jvm bin/coro.jar
+--cmd java -jar bin/coro.jar
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-lua.hxml b/tests/misc/coroutines/build-lua.hxml
new file mode 100644
index 00000000000..178e2f3be48
--- /dev/null
+++ b/tests/misc/coroutines/build-lua.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--lua bin/coro.lua
+--cmd lua bin/coro.lua
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-neko.hxml b/tests/misc/coroutines/build-neko.hxml
new file mode 100644
index 00000000000..f42ea5fed25
--- /dev/null
+++ b/tests/misc/coroutines/build-neko.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--neko bin/coro.n
+--cmd neko bin/coro.n
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-php.hxml b/tests/misc/coroutines/build-php.hxml
new file mode 100644
index 00000000000..d75f939b821
--- /dev/null
+++ b/tests/misc/coroutines/build-php.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--php bin/php
+--cmd php bin/php/index.php
\ No newline at end of file
diff --git a/tests/misc/coroutines/build-python.hxml b/tests/misc/coroutines/build-python.hxml
new file mode 100644
index 00000000000..e1c254dc524
--- /dev/null
+++ b/tests/misc/coroutines/build-python.hxml
@@ -0,0 +1,3 @@
+build-base.hxml
+--python bin/coro.py
+--cmd python bin/coro.py
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/BaseCase.hx b/tests/misc/coroutines/src/BaseCase.hx
new file mode 100644
index 00000000000..0a447a46b23
--- /dev/null
+++ b/tests/misc/coroutines/src/BaseCase.hx
@@ -0,0 +1,19 @@
+@:keepSub
+@:keep
+class BaseCase implements utest.ITest {
+ var dummy:String = '';
+
+ public function new() {}
+
+ public function setup() {
+ dummy = '';
+ }
+
+ function assert(expected:Array, generator:Iterator, ?p:haxe.PosInfos) {
+ dummy = '';
+ for (it in generator) {
+ Assert.equals(expected.shift(), it, p);
+ }
+ Assert.equals(0, expected.length, p);
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/Helper.hx b/tests/misc/coroutines/src/Helper.hx
new file mode 100644
index 00000000000..6f1c2ab3117
--- /dev/null
+++ b/tests/misc/coroutines/src/Helper.hx
@@ -0,0 +1,4 @@
+@:coroutine
+function mapCalls(args:Array, f:CoroutineTRet>):Array {
+ return [for (arg in args) f(arg)];
+}
diff --git a/tests/misc/coroutines/src/Main.hx b/tests/misc/coroutines/src/Main.hx
new file mode 100644
index 00000000000..a89dbbf3cb7
--- /dev/null
+++ b/tests/misc/coroutines/src/Main.hx
@@ -0,0 +1,38 @@
+import yield.*;
+
+function main() {
+
+ var cases = [
+ new TestBasic(),
+ new TestTricky(),
+ new TestControlFlow(),
+ new TestTryCatch(),
+ new TestHoisting(),
+ new TestMisc(),
+ new TestMutex(),
+ // new TestGenerator(),
+ #if js
+ new TestJsPromise(),
+ #end
+ #if (!coroutine.throw && (jvm || cpp || eval))
+ new TestCallStack(),
+ #end
+ // new TestYieldBasic(),
+ // new TestYieldIf(),
+ // new TestYieldFor(),
+ // new TestYieldClosure(),
+ // new TestYieldSwitch(),
+ // new TestYieldTryCatch(),
+ // new TestYieldWhile(),
+ ];
+
+ var runner = new utest.Runner();
+
+ for (eachCase in cases) {
+ runner.addCase(eachCase);
+ }
+ runner.addCases("issues");
+
+ utest.ui.Report.create(runner);
+ runner.run();
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/TestBasic.hx b/tests/misc/coroutines/src/TestBasic.hx
new file mode 100644
index 00000000000..12e2a84a341
--- /dev/null
+++ b/tests/misc/coroutines/src/TestBasic.hx
@@ -0,0 +1,71 @@
+import haxe.Exception;
+import haxe.coro.Coroutine.yield;
+
+class TestBasic extends utest.Test {
+ function testSimple() {
+ Assert.equals(42, Coroutine.run(@:coroutine function run() {
+ return simple(42);
+ }));
+ }
+
+ function testErrorDirect() {
+ Assert.raises(() -> Coroutine.run(error), String);
+ }
+
+ function testErrorPropagation() {
+ @:coroutine function propagate() {
+ error();
+ }
+
+ Assert.raises(() -> Coroutine.run(propagate), String);
+ }
+
+ function testResumeWithError() {
+ @:coroutine function foo() {
+ Coroutine.suspend(cont -> {
+ cont.resume(null, new Exception(""));
+ });
+ }
+
+ Assert.raises(() -> Coroutine.run(foo), Exception);
+ }
+
+ function testUnnamedLocalCoroutines() {
+ final c1 = @:coroutine function () {
+ yield();
+
+ return 10;
+ };
+
+ Assert.equals(10, Coroutine.run(c1));
+ }
+
+ function testLocalTypeParameters() {
+ Coroutine.run(@:coroutine function f():T {
+ return null;
+ });
+ Assert.pass(); // The test is that this doesn't cause an unbound type parameter
+ }
+
+ #if sys
+
+ function testDelay() {
+ var elapsed = Coroutine.run(() -> {
+ var start = Sys.time();
+ Coroutine.delay(500);
+ return Sys.time() - start;
+ });
+ // This might not be super accurate, but it's good enough
+ Assert.isTrue(elapsed > 0.4);
+ }
+
+ #end
+
+ @:coroutine static function simple(arg:Int):Int {
+ return arg;
+ }
+
+ @:coroutine static function error() {
+ throw "nope";
+ }
+}
diff --git a/tests/misc/coroutines/src/TestCallStack.hx b/tests/misc/coroutines/src/TestCallStack.hx
new file mode 100644
index 00000000000..3b4e322611f
--- /dev/null
+++ b/tests/misc/coroutines/src/TestCallStack.hx
@@ -0,0 +1,43 @@
+import callstack.CallStackInspector;
+
+class TestCallStack extends utest.Test {
+ function test() {
+ try {
+ callstack.Bottom.entry();
+ Assert.fail("Exception expected");
+ } catch(e:haxe.exceptions.NotImplementedException) {
+ var inspector = new CallStackInspector(e.stack.asArray());
+ var r = inspector.inspect([
+ File('callstack/Top.hx'),
+ Line(4),
+ Line(8),
+ Line(12),
+ File('callstack/CoroUpper.hx'),
+ Line(10),
+ #if hl
+ Line(5), // I still don't think this should be here
+ #end
+ Line(8),
+ Line(8),
+ Line(8),
+ Line(8),
+ Line(17),
+ Skip('callstack/SyncMiddle.hx'),
+ Line(4),
+ Line(8),
+ File('callstack/CoroLower.hx'),
+ Line(8),
+ Skip('callstack/Bottom.hx'),
+ Line(4)
+
+ ]);
+ if (r == null) {
+ Assert.pass();
+ } else {
+ var i = 0;
+ var lines = e.stack.asArray().map(item -> '\t[${i++}] $item');
+ Assert.fail('${r.toString()}\n${lines.join("\n")}');
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/TestControlFlow.hx b/tests/misc/coroutines/src/TestControlFlow.hx
new file mode 100644
index 00000000000..3337f843565
--- /dev/null
+++ b/tests/misc/coroutines/src/TestControlFlow.hx
@@ -0,0 +1,124 @@
+import Helper;
+
+import haxe.coro.Coroutine.yield;
+
+class TestControlFlow extends utest.Test {
+ function testIfThen() {
+ @:coroutine function f(x) {
+ if (x) return 1;
+ return 2;
+ }
+
+ Assert.same(Coroutine.run(@:coroutine function run() {
+ return mapCalls([ true, false ], f);
+ }), [ 1, 2 ]);
+ }
+
+ function testIfThenReturnNoValue() {
+ var v = null;
+ @:coroutine function f(x) {
+ v = 1;
+ if (x) {
+ return;
+ }
+ v = 2;
+ }
+ @:coroutine function f2(x) { f(x); return v; }
+
+ Assert.same(Coroutine.run(@:coroutine function run() {
+ return mapCalls([ true, false ], f2);
+ }), [ 1, 2 ]);
+ }
+
+ function testIfThenElse() {
+ @:coroutine function f(x) {
+ return if (x) 1 else 2;
+ }
+
+ Assert.same(Coroutine.run(@:coroutine function run() {
+ return mapCalls([ true, false ], f);
+ }), [ 1, 2 ]);
+ }
+
+ function testSwitchNoDefault() {
+ @:coroutine function f(x) {
+ switch (x) {
+ case 1: return "a";
+ case 2: return "b";
+ case 3: return "c";
+ }
+ return "d";
+ }
+
+ Assert.same(Coroutine.run(@:coroutine function run() {
+ return mapCalls([ 1, 2, 3, 4 ], f);
+ }), ["a", "b", "c", "d"]);
+ }
+
+ function testSwitchDefault() {
+ @:coroutine function f(x) {
+ switch (x) {
+ case 1: return "a";
+ case 2: return "b";
+ case 3: return "c";
+ default: return "d";
+ }
+ return "e";
+ }
+ Assert.same(Coroutine.run(@:coroutine function run() {
+ return mapCalls([ 1, 2, 3, 4 ], f);
+ }), ["a", "b", "c", "d"]);
+ }
+
+ function testLoop() {
+ @:coroutine function f(x) {
+ var results = [];
+ var i = 0;
+ while (i < 10) {
+ if (i == 5 && x == 1) break;
+ if (i == 6 && x == 2) { i++; continue; }
+ results.push(i);
+ i++;
+ }
+ return results;
+ }
+ Assert.same([
+ [0,1,2,3,4,5,6,7,8,9],
+ [0,1,2,3,4],
+ [0,1,2,3,4,5,7,8,9]
+ ], Coroutine.run(@:coroutine function run() {
+ return mapCalls([ 0, 1, 2 ], f);
+ }));
+ }
+
+ function testRecursion() {
+ var maxIters = 3;
+ var counter = 0;
+
+ @:coroutine function foo() {
+ if (++counter < maxIters) {
+ foo();
+ }
+ }
+
+ Coroutine.run(foo);
+
+ Assert.equals(counter, maxIters);
+ }
+
+ function testSuspendingRecursion() {
+ var maxIters = 3;
+ var counter = 0;
+
+ @:coroutine function foo() {
+ if (++counter < maxIters) {
+ yield();
+ foo();
+ }
+ }
+
+ Coroutine.run(foo);
+
+ Assert.equals(counter, maxIters);
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/TestGenerator.hx b/tests/misc/coroutines/src/TestGenerator.hx
new file mode 100644
index 00000000000..e224128001d
--- /dev/null
+++ b/tests/misc/coroutines/src/TestGenerator.hx
@@ -0,0 +1,45 @@
+import yield.Yield;
+
+class TestGenerator extends utest.Test {
+ function testSimple() {
+ var iter = sequence(yield -> {
+ yield(1);
+ yield(2);
+ yield(3);
+ });
+ Assert.same([1,2,3], [for (v in iter) v]);
+ }
+
+ function testTreeIter() {
+ @:coroutine function iterTreeRec(yield:Yield, tree:Tree) {
+ yield(tree.leaf);
+ if (tree.left != null) iterTreeRec(yield, tree.left);
+ if (tree.right != null) iterTreeRec(yield, tree.right);
+ }
+
+ function iterTree(tree:Tree):Iterator {
+ return sequence(yield -> iterTreeRec(yield, tree));
+ }
+
+ var tree:Tree = {
+ leaf: 1,
+ left: {
+ leaf: 2,
+ left: {leaf: 3},
+ right: {leaf: 4, left: {leaf: 5}},
+ },
+ right: {
+ leaf: 6,
+ left: {leaf: 7}
+ }
+ };
+
+ Assert.same([1,2,3,4,5,6,7], [for (v in iterTree(tree)) v]);
+ }
+}
+
+private typedef Tree = {
+ var leaf:T;
+ var ?left:Tree;
+ var ?right:Tree;
+}
diff --git a/tests/misc/coroutines/src/TestHoisting.hx b/tests/misc/coroutines/src/TestHoisting.hx
new file mode 100644
index 00000000000..24261b72e31
--- /dev/null
+++ b/tests/misc/coroutines/src/TestHoisting.hx
@@ -0,0 +1,118 @@
+import haxe.coro.Coroutine.yield;
+
+class TestHoisting extends utest.Test {
+ function testLocalVariable() {
+
+ @:coroutine function foo() {
+ var bar = 7;
+
+ yield();
+
+ return bar;
+ }
+
+ Assert.equals(7, Coroutine.run(foo));
+ }
+
+ function testModifyingLocalVariable() {
+ @:coroutine function foo() {
+ var bar = 7;
+
+ yield();
+
+ bar *= 2;
+
+ yield();
+
+ return bar;
+ }
+
+ Assert.equals(14, Coroutine.run(foo));
+ }
+
+ @:coroutine function fooTestArgument(v:Int) {
+ yield();
+
+ return v;
+ }
+
+ function testArgument() {
+ Assert.equals(7, Coroutine.run(() -> {
+ return fooTestArgument(7);
+ }));
+ }
+
+ function testLocalArgument() {
+ Assert.equals(7, Coroutine.run(() -> {
+ @:coroutine function foo(v:Int) {
+ yield();
+
+ return v;
+ }
+
+ return foo(7);
+ }));
+ }
+
+ @:coroutine function fooTestModifyingArgument(v:Int) {
+ yield();
+
+ v *= 2;
+
+ yield();
+
+ return v;
+ }
+
+ function testModifyingArgument() {
+ Assert.equals(14, Coroutine.run(() -> {
+ return fooTestModifyingArgument(7);
+ }));
+ }
+
+ function testModifyingLocalArgument() {
+ Assert.equals(14, Coroutine.run(() -> {
+ @:coroutine function foo(v:Int) {
+ yield();
+
+ v *= 2;
+
+ yield();
+
+ return v;
+ }
+
+ return foo(7);
+ }));
+ }
+
+ function testCapturingLocal() {
+ var i = 0;
+
+ Coroutine.run(() -> {
+ i = 7;
+ yield();
+ i *= 2;
+ });
+
+ Assert.equals(14, i);
+ }
+
+ function testMultiHoisting() {
+ Assert.equals(14, Coroutine.run(() -> {
+
+ var i = 0;
+
+ @:coroutine function foo() {
+ yield();
+
+ i = 7;
+ }
+
+ foo();
+
+ return i * 2;
+
+ }));
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/TestJsPromise.hx b/tests/misc/coroutines/src/TestJsPromise.hx
new file mode 100644
index 00000000000..731f621bc95
--- /dev/null
+++ b/tests/misc/coroutines/src/TestJsPromise.hx
@@ -0,0 +1,89 @@
+import js.lib.Error;
+import js.lib.Promise;
+
+using TestJsPromise.CoroTools;
+
+class CoroTools {
+ static public function start(c:Coroutine<() -> T>, f:(T, E) -> Void) {
+ try {
+ f(Coroutine.run(c), null);
+ } catch(e:Dynamic) {
+ f(null, e);
+ }
+ }
+}
+
+@:coroutine
+private function await(p:Promise) {
+ Coroutine.suspend(cont -> p.then(r -> cont.resume(r, null), e -> cont.resume(null, e)));
+}
+
+private function promise(c:Coroutine<()->T>):Promise {
+ return new Promise((resolve,reject) -> c.start((result, error) -> if (error != null) reject(error) else resolve(result)));
+}
+
+class TestJsPromise extends utest.Test {
+ // function testAwait(async:Async) {
+ // var p = Promise.resolve(41);
+
+ // @:coroutine function awaiting() {
+ // var x = await(p);
+ // return x + 1;
+ // }
+
+ // awaiting.start((result,error) -> {
+ // Assert.equals(42, result);
+ // async.done();
+ // });
+ // }
+
+ function testPromise(async:Async) {
+ var p = promise(() -> 42);
+ p.then(result -> {
+ Assert.equals(42, result);
+ async.done();
+ });
+ }
+
+ // function testAsyncAwait(async:Async) {
+ // var p1 = Promise.resolve(41);
+
+ // var p2 = promise(() -> {
+ // var x = await(p1);
+ // return x + 1;
+ // });
+
+ // p2.then(result -> {
+ // Assert.equals(42, result);
+ // 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();
+ }
+ );
+ }
+}
diff --git a/tests/misc/coroutines/src/TestMisc.hx b/tests/misc/coroutines/src/TestMisc.hx
new file mode 100644
index 00000000000..b7961c4be3c
--- /dev/null
+++ b/tests/misc/coroutines/src/TestMisc.hx
@@ -0,0 +1,13 @@
+import haxe.coro.Coroutine.yield;
+
+class TestMisc extends utest.Test {
+ function testDebugMetadataLocalFunction() {
+ @:coroutine @:coroutine.debgu function foo() {
+ yield();
+ }
+
+ Coroutine.run(foo);
+
+ Assert.pass();
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/TestMutex.hx b/tests/misc/coroutines/src/TestMutex.hx
new file mode 100644
index 00000000000..f4f978a6f15
--- /dev/null
+++ b/tests/misc/coroutines/src/TestMutex.hx
@@ -0,0 +1,11 @@
+import haxe.coro.Mutex;
+
+class TestMutex extends utest.Test {
+ function testSimple() {
+ final m = new Mutex();
+ m.acquire();
+ m.release();
+ Assert.equals(true, m.tryAcquire());
+ m.release();
+ }
+}
diff --git a/tests/misc/coroutines/src/TestTricky.hx b/tests/misc/coroutines/src/TestTricky.hx
new file mode 100644
index 00000000000..708803376f5
--- /dev/null
+++ b/tests/misc/coroutines/src/TestTricky.hx
@@ -0,0 +1,28 @@
+class CoroFile {
+ public final file:String;
+
+ public function new(file) {
+ this.file = file;
+ }
+
+ @:coroutine public function write() {
+ return file;
+ }
+
+ @:coroutine public function almostWrite() {
+ return () -> file;
+ }
+}
+
+class TestTricky extends utest.Test {
+ function testCapturedThis() {
+ final file = new CoroFile("value");
+ Assert.equals("value", cast Coroutine.run(file.write));
+ }
+
+ function testPreviouslyCapturedThis() {
+ final file = new CoroFile("value");
+ final func : ()->String = cast Coroutine.run(file.almostWrite);
+ Assert.equals("value", func());
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/TestTryCatch.hx b/tests/misc/coroutines/src/TestTryCatch.hx
new file mode 100644
index 00000000000..d841fe9e057
--- /dev/null
+++ b/tests/misc/coroutines/src/TestTryCatch.hx
@@ -0,0 +1,274 @@
+import haxe.coro.Coroutine.yield;
+import Helper;
+
+class TestTryCatch extends utest.Test {
+ function testTryCatch() {
+ Assert.same(["e1", "e2"], Coroutine.run(@:coroutine function run() {
+ return mapCalls([new E1(), new E2()], tryCatch);
+ }));
+ }
+
+ function testTryCatchFail() {
+ Assert.raises(() -> Coroutine.run(@:coroutine function run() {
+ return tryCatch(new E3());
+ }), E3);
+ }
+
+ function testTryCatchNonExc() {
+ Assert.same(["ne1", "ne2"], Coroutine.run(@:coroutine function run() {
+ return mapCalls([new NE1(), new NE2()], tryCatchNonExc);
+ }));
+ }
+
+ function testTryCatchNonExcFail() {
+ Assert.raises(() -> Coroutine.run(@:coroutine function run() {
+ return tryCatchNonExc(new NE3());
+ }), NE3);
+ }
+
+ function testTryCatchMixed() {
+ Assert.same(["e1", "e2", "ne1", "ne2"], Coroutine.run(@:coroutine function run() {
+ return mapCalls(([new E1(), new E2(), new NE1(), new NE2()] : Array), tryCatchMixed);
+ }));
+ }
+
+ function testTryCatchMixedFail() {
+ Assert.raises(() -> Coroutine.run(@:coroutine function run() {
+ return tryCatchMixed("foo");
+ }), String);
+ Assert.raises(() -> Coroutine.run(@:coroutine function run() {
+ return tryCatchMixed(new E3());
+ }), E3);
+ Assert.raises(() -> Coroutine.run(@:coroutine function run() {
+ return tryCatchMixed(new NE3());
+ }), NE3);
+ }
+
+ function testTryCatchNoCatch() {
+ @:coroutine function f(yield:CoroutineVoid>) {
+ var dummy = '1';
+ try {
+ dummy += '2';
+ yield(10);
+ dummy += '3';
+ } catch (e:Dynamic) {
+ dummy += '4';
+ }
+ dummy += '5';
+ return dummy;
+ }
+ var a = [];
+ Assert.equals("1235", Coroutine.run(() -> f(i -> a.push(i))));
+ Assert.same([10], a);
+ a = [];
+ Assert.equals("1245", Coroutine.run(() -> f(i -> throw i)));
+ Assert.same([], a);
+ }
+
+ function testTryCatchOneCatch() {
+ @:coroutine function f(yield:CoroutineVoid>) {
+ var dummy = '1';
+ try {
+ dummy += '2';
+ throw 'Error!';
+ dummy += '3';
+ } catch (e:Dynamic) {
+ dummy += '4';
+ yield(10);
+ dummy += '5';
+ }
+ dummy += '6';
+ return dummy;
+ }
+ var a = [];
+ Assert.equals("12456", Coroutine.run(() -> f(i -> a.push(i))));
+ Assert.same([10], a);
+ }
+
+ function testTryCatchMultiCatch() {
+ @:coroutine function f(yield:CoroutineVoid>, throwValue:Dynamic) {
+ var dummy = '1';
+ try {
+ dummy += '2';
+ throw throwValue;
+ dummy += '3';
+ } catch (e:String) {
+ dummy += '4';
+ yield(10);
+ dummy += '5';
+ } catch (e:Dynamic) {
+ dummy += '6';
+ yield(20);
+ dummy += '7';
+ }
+ dummy += '8';
+ return dummy;
+ }
+ var a = [];
+ Assert.equals("12458", Coroutine.run(() -> f(i -> a.push(i), 'Error')));
+ Assert.same([10], a);
+ a = [];
+ Assert.equals("12678", Coroutine.run(() -> f(i -> a.push(i), 123)));
+ Assert.same([20], a);
+ }
+
+ function testTryCatchNested() {
+ @:coroutine function f(yield:CoroutineVoid>, throwValue:Dynamic) {
+ var dummy = '1';
+ try {
+ try {
+ dummy += '2';
+ throw throwValue;
+ dummy += '3';
+ } catch (e:Int) {
+ dummy += '4';
+ yield("10");
+ dummy += '5';
+ }
+ dummy += '6';
+ } catch (e:Dynamic) {
+ dummy += '7';
+ yield('caught: $e, dummy: $dummy');
+ dummy += '8';
+ }
+ dummy += '9';
+ return dummy;
+ }
+ var a = [];
+ Assert.equals("124569", Coroutine.run(() -> f(i -> a.push(i), 1)));
+ Assert.same(["10"], a);
+ a = [];
+ Assert.equals("12789", Coroutine.run(() -> f(i -> a.push(i), "foo")));
+ Assert.same(["caught: foo, dummy: 127"], a);
+ a = [];
+ Assert.equals("124789", Coroutine.run(() -> f(i -> i == "10"?throw i:a.push(i), 1)));
+ Assert.same(["caught: 10, dummy: 1247"], a);
+ final yieldThrow = @:coroutine i -> throw i;
+ // TODO: gives "Cannot use Void as value" without the explicit :Void type-hint
+ final yieldThrowInChildCoro = @:coroutine function(i):Void return Coroutine.run(() -> throw i);
+ for (yield in [yieldThrow, yieldThrowInChildCoro]) {
+ try {
+ Coroutine.run(() -> f(yield, "foo"));
+ Assert.fail();
+ } catch (e:String) {
+ Assert.equals('caught: foo, dummy: 127', e);
+ }
+ try {
+ Coroutine.run(() -> f(yield, 1));
+ Assert.fail();
+ } catch (e:String) {
+ Assert.equals('caught: 10, dummy: 1247', e);
+ }
+ }
+ }
+
+ function testTryCatchExceptionNotCaughtThrownOutOfYieldContext() { // wtf?
+ var dummy = '1';
+ @:coroutine function f(yield:CoroutineVoid>) {
+ try {
+ dummy += '2';
+ throw "Error!";
+ dummy += '3';
+ yield(10);
+ dummy += '4';
+ } catch (e:Int) {
+ dummy += '5';
+ }
+ dummy += '6';
+ return dummy;
+ }
+ try {
+ Coroutine.run(() -> f(i -> Assert.fail()));
+ Assert.fail();
+ } catch (e:String) {
+ Assert.equals('Error!', e);
+ Assert.equals('12', dummy);
+ }
+ }
+
+ function testTryCatchYieldCapture() {
+ @:coroutine function f(yield:CoroutineVoid>) {
+ var dummy = '1';
+ try {
+ dummy += '2';
+ throw 10;
+ dummy += '3';
+ } catch (e:Int) {
+ dummy += '4';
+ yield(e);
+ dummy += '5';
+ }
+ dummy += '6';
+ return dummy;
+ }
+ var a = [];
+ Assert.equals("12456", Coroutine.run(() -> f(i -> a.push(i))));
+ Assert.same([10], a);
+ }
+
+ @:coroutine function tryCatch(e:haxe.Exception) {
+ try {
+ throw e;
+ } catch (e:E1) {
+ return "e1";
+ } catch (e:E2) {
+ return "e2";
+ }
+ return "none";
+ }
+
+ @:coroutine function tryCatchNonExc(e:NE) {
+ try {
+ throw e;
+ } catch (e:NE1) {
+ return "ne1";
+ } catch (e:NE2) {
+ return "ne2";
+ }
+ return "none";
+ }
+
+ @:coroutine function tryCatchMixed(e:Any) {
+ try {
+ throw e;
+ } catch (e:E1) {
+ return "e1";
+ } catch (e:E2) {
+ return "e2";
+ } catch (e:NE1) {
+ return "ne1";
+ } catch (e:NE2) {
+ return "ne2";
+ }
+ return "none";
+ }
+}
+
+private class E1 extends haxe.Exception {
+ public function new()
+ super("E1");
+}
+
+private class E2 extends haxe.Exception {
+ public function new()
+ super("E2");
+}
+
+private class E3 extends haxe.Exception {
+ public function new()
+ super("E3");
+}
+
+interface NE {}
+
+private class NE1 implements NE {
+ public function new() {};
+}
+
+private class NE2 implements NE {
+ public function new() {};
+}
+
+private class NE3 implements NE {
+ public function new() {};
+}
diff --git a/tests/misc/coroutines/src/callstack/Bottom.hx b/tests/misc/coroutines/src/callstack/Bottom.hx
new file mode 100644
index 00000000000..9d2d116ff21
--- /dev/null
+++ b/tests/misc/coroutines/src/callstack/Bottom.hx
@@ -0,0 +1,5 @@
+package callstack;
+
+function entry() {
+ Coroutine.run(() -> CoroLower.foo());
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/callstack/CallStackInspector.hx b/tests/misc/coroutines/src/callstack/CallStackInspector.hx
new file mode 100644
index 00000000000..5a6e6b9a5d6
--- /dev/null
+++ b/tests/misc/coroutines/src/callstack/CallStackInspector.hx
@@ -0,0 +1,82 @@
+package callstack;
+
+import haxe.CallStack;
+using StringTools;
+
+enum CallStackInspect {
+ File(file:String);
+ Line(line:Int);
+ Skip(file:String);
+}
+
+class CallStackInspectorFailure extends haxe.Exception {
+ public function new(reason:String) {
+ super(reason);
+ }
+}
+
+class CallStackInspector {
+ final stack:Array;
+ var offset:Int;
+ var expectedFile:Null;
+ var performedTests:Int;
+ var inspectOffset:Int;
+
+ public function new(stack:Array) {
+ this.stack = stack;
+ offset = 0;
+ inspectOffset = -1;
+ performedTests = 0;
+ }
+
+ public function inspect(items:Array) {
+ try {
+ for (item in items) {
+ doInspect(item);
+ }
+ return null;
+ } catch (e:CallStackInspectorFailure) {
+ return e;
+ }
+ }
+
+ function fail(inspect: CallStackInspect, reason:String) {
+ throw new CallStackInspectorFailure('Failure at stack offset $offset, inspect offset $inspectOffset with $inspect: $reason');
+ }
+
+ function doInspect(inspect:CallStackInspect) {
+ ++inspectOffset;
+ switch (inspect) {
+ case File(file):
+ this.expectedFile = file;
+ case Line(expectedLine):
+ final index = offset++;
+ switch (stack[index]) {
+ case FilePos(_, file, line):
+ if (!file.endsWith(expectedFile)) {
+ fail(inspect, 'file $file should be $expectedFile');
+ }
+ performedTests++;
+ if (line != expectedLine) {
+ fail(inspect, 'line $line should be $expectedLine');
+ }
+ performedTests++;
+ case v:
+ fail(inspect, '$v should be FilePos');
+ }
+ case Skip(file):
+ while (true) {
+ if (offset == stack.length) {
+ fail(inspect, '$offset went out of bounds while skipping until $file');
+ }
+ switch (stack[offset]) {
+ case FilePos(Method(_), file2, _) if (file2.endsWith(file)):
+ expectedFile = file;
+ break;
+ case _:
+ offset++;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/callstack/CoroLower.hx b/tests/misc/coroutines/src/callstack/CoroLower.hx
new file mode 100644
index 00000000000..f6d6003a631
--- /dev/null
+++ b/tests/misc/coroutines/src/callstack/CoroLower.hx
@@ -0,0 +1,9 @@
+package callstack;
+
+import haxe.coro.Coroutine.yield;
+
+@:coroutine function foo() {
+ yield();
+
+ SyncMiddle.syncFun1();
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/callstack/CoroUpper.hx b/tests/misc/coroutines/src/callstack/CoroUpper.hx
new file mode 100644
index 00000000000..00a9b2127c1
--- /dev/null
+++ b/tests/misc/coroutines/src/callstack/CoroUpper.hx
@@ -0,0 +1,18 @@
+package callstack;
+
+import haxe.coro.Coroutine.yield;
+
+@:coroutine function recursion(i:Int, acc:Int) {
+ yield();
+ return if (i > 0) {
+ recursion(i - 1, acc + i);
+ } else {
+ Top.topCall1();
+ }
+}
+
+@:coroutine function bar() {
+ yield();
+
+ recursion(4, 0);
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/callstack/SyncMiddle.hx b/tests/misc/coroutines/src/callstack/SyncMiddle.hx
new file mode 100644
index 00000000000..f1d323252fd
--- /dev/null
+++ b/tests/misc/coroutines/src/callstack/SyncMiddle.hx
@@ -0,0 +1,9 @@
+package callstack;
+
+function syncFun2() {
+ Coroutine.run(() -> CoroUpper.bar());
+}
+
+function syncFun1() {
+ syncFun2();
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/callstack/Top.hx b/tests/misc/coroutines/src/callstack/Top.hx
new file mode 100644
index 00000000000..0f4d81400c6
--- /dev/null
+++ b/tests/misc/coroutines/src/callstack/Top.hx
@@ -0,0 +1,13 @@
+package callstack;
+
+function throwing() {
+ throw new haxe.exceptions.NotImplementedException();
+}
+
+function topCall2() {
+ throwing();
+}
+
+function topCall1() {
+ topCall2();
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/import.hx b/tests/misc/coroutines/src/import.hx
new file mode 100644
index 00000000000..a7e1d0bcaa3
--- /dev/null
+++ b/tests/misc/coroutines/src/import.hx
@@ -0,0 +1,3 @@
+import utest.Assert;
+import utest.Async;
+import haxe.coro.Coroutine;
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue24.hx b/tests/misc/coroutines/src/issues/aidan/Issue24.hx
new file mode 100644
index 00000000000..bc897e6e6e7
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue24.hx
@@ -0,0 +1,24 @@
+package issues.aidan;
+
+class MyCont {
+ public function new() {}
+
+ public function getOrThrow():Any {
+ return "foo";
+ }
+}
+
+@:coroutine
+private function await() {
+ var safe = new MyCont();
+ return {
+ var this1 = safe.getOrThrow();
+ this1;
+ };
+}
+
+class Issue24 extends utest.Test {
+ function test() {
+ Assert.equals("foo", Coroutine.run(await));
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue27.hx b/tests/misc/coroutines/src/issues/aidan/Issue27.hx
new file mode 100644
index 00000000000..3cd23c058e0
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue27.hx
@@ -0,0 +1,61 @@
+package issues.aidan;
+import haxe.coro.context.Key;
+import haxe.coro.context.IElement;
+import haxe.coro.Coroutine;
+
+class DebugName implements IElement {
+ static public var key:Key = Key.createNew("DebugName");
+
+ public var name:String;
+
+ public function new(name:String) {
+ this.name = name;
+ }
+
+ public function getKey() {
+ return key;
+ }
+
+ public function toString() {
+ return '[DebugName: $name]';
+ }
+}
+
+class Issue27 extends utest.Test {
+ function test() {
+ @:coroutine
+ function setDebug(name:String) {
+ Coroutine.suspend(cont -> {
+ cont.context.set(DebugName.key, new DebugName(name));
+ cont.resume(null, null);
+ });
+ }
+
+ var log = [];
+
+ @:coroutine
+ function logDebug() {
+ Coroutine.suspend(cont -> {
+ log.push(cont.context.get(DebugName.key).name);
+ cont.resume(null, null);
+ });
+ }
+
+ @:coroutine
+ function modifyDebug(name:String) {
+ Coroutine.suspend(cont -> {
+ cont.context.get(DebugName.key).name = name;
+ cont.resume(null, null);
+ });
+ }
+ @:coroutine
+ function test() {
+ setDebug("first name");
+ logDebug();
+ modifyDebug("second name");
+ logDebug();
+ return log.join(", ");
+ }
+ Assert.equals("first name, second name", Coroutine.run(test));
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue38.hx b/tests/misc/coroutines/src/issues/aidan/Issue38.hx
new file mode 100644
index 00000000000..610b9b197a7
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue38.hx
@@ -0,0 +1,13 @@
+package issues.aidan;
+
+@:coroutine function foo() : String {
+ return Coroutine.suspend(cont -> {
+ cont.resume('Hello, World!', null);
+ });
+}
+
+class Issue38 extends utest.Test {
+ function test() {
+ Assert.equals("Hello, World!", Coroutine.run(foo));
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue54.hx b/tests/misc/coroutines/src/issues/aidan/Issue54.hx
new file mode 100644
index 00000000000..528337f5874
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue54.hx
@@ -0,0 +1,21 @@
+package issues.aidan;
+
+@:coroutine function suspendThenThrow() {
+ Coroutine.delay(1);
+ throw "fail";
+}
+
+@:coroutine function f() {
+ try {
+ suspendThenThrow();
+ return "wrong";
+ } catch (e:Dynamic) {
+ return 'caught: $e';
+ }
+}
+
+class Issue54 extends utest.Test {
+ public function test() {
+ Assert.equals("caught: fail", Coroutine.run(f));
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue55.hx b/tests/misc/coroutines/src/issues/aidan/Issue55.hx
new file mode 100644
index 00000000000..30a1e892b3f
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue55.hx
@@ -0,0 +1,24 @@
+package issues.aidan;
+
+import haxe.exceptions.NotImplementedException;
+
+function throwing(v:Dynamic) {
+ throw v;
+}
+
+@:coroutine function foo(v:Dynamic) {
+ var s = try {
+ throwing(v);
+ "";
+ } catch (s:String) {
+ s;
+ }
+ return s;
+}
+
+class Issue55 extends utest.Test {
+ public function test() {
+ Assert.equals("caught", Coroutine.run(() -> foo("caught")));
+ Assert.raises(() -> Coroutine.run(() -> foo(new haxe.exceptions.NotImplementedException())), NotImplementedException);
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue59.hx b/tests/misc/coroutines/src/issues/aidan/Issue59.hx
new file mode 100644
index 00000000000..e1cbfcd8387
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue59.hx
@@ -0,0 +1,23 @@
+package issues.aidan;
+
+import haxe.coro.Coroutine;
+import haxe.coro.Coroutine.yield;
+import haxe.exceptions.NotImplementedException;
+
+function throwing() {
+ throw new NotImplementedException();
+}
+
+@:coroutine function recursion(i:Int, acc:Int) {
+ yield();
+ return if (i > 0) {
+ recursion(i - 1, acc + i);
+ } else {
+ throwing();
+ }
+}
+class Issue59 extends utest.Test {
+ public function test() {
+ Assert.raises(() -> Coroutine.run(() -> recursion(2, 0)), NotImplementedException);
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue61.hx b/tests/misc/coroutines/src/issues/aidan/Issue61.hx
new file mode 100644
index 00000000000..348715dbd4f
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue61.hx
@@ -0,0 +1,21 @@
+package issues.aidan;
+
+import utest.Assert;
+import haxe.coro.Coroutine;
+import haxe.coro.Coroutine.yield;
+
+class Issue61 extends utest.Test {
+ public function test() {
+ Coroutine.run(foo);
+ }
+
+ @:coroutine function foo() {
+ var a = 2;
+ yield();
+ Assert.equals(2, a);
+
+ var a = 1;
+ yield();
+ Assert.equals(1, a);
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue69.hx b/tests/misc/coroutines/src/issues/aidan/Issue69.hx
new file mode 100644
index 00000000000..6dabd22989d
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue69.hx
@@ -0,0 +1,29 @@
+package issues.aidan;
+
+import utest.Assert;
+import haxe.coro.Coroutine;
+import haxe.coro.Coroutine.yield;
+
+private interface IFoo {
+ @:coroutine function bar():Void;
+}
+
+private class Foo implements IFoo {
+ public function new() {}
+
+ @:coroutine public function bar() {
+ yield();
+ }
+}
+
+class Issue69 extends utest.Test {
+ public function test() {
+ Coroutine.run(() -> {
+ final f : IFoo = new Foo();
+
+ f.bar();
+ });
+
+ Assert.pass();
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue75.hx b/tests/misc/coroutines/src/issues/aidan/Issue75.hx
new file mode 100644
index 00000000000..26c5eee3017
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue75.hx
@@ -0,0 +1,28 @@
+package issues.aidan;
+
+import utest.Assert;
+import haxe.Exception;
+import haxe.coro.Coroutine;
+import haxe.coro.Coroutine.yield;
+
+@:coroutine function foo() {
+ Coroutine.suspend(cont -> {
+ cont.resume(null, new Exception("error"));
+ });
+}
+
+class Issue75 extends utest.Test {
+ public function test() {
+ var s = "";
+ Coroutine.run(() -> {
+ try {
+ foo();
+ } catch (_:Dynamic) {
+ s += 'caught';
+ }
+
+ s += 'done';
+ });
+ Assert.equals("caughtdone", s);
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/issues/aidan/Issue79.hx b/tests/misc/coroutines/src/issues/aidan/Issue79.hx
new file mode 100644
index 00000000000..ae47922e84e
--- /dev/null
+++ b/tests/misc/coroutines/src/issues/aidan/Issue79.hx
@@ -0,0 +1,16 @@
+package issues.aidan;
+
+function someCall(v:Dynamic) {}
+
+class Issue79 extends utest.Test {
+ function test() {
+ Coroutine.run(function() {
+ someCall({
+ var a = 1;
+ someCall(a);
+ a;
+ });
+ });
+ Assert.pass();
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/TestYieldBasic.hx b/tests/misc/coroutines/src/yield/TestYieldBasic.hx
new file mode 100644
index 00000000000..7b62bd50186
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/TestYieldBasic.hx
@@ -0,0 +1,126 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldBasic extends BaseCase {
+ public function testBasicYieldReturn() {
+ assert([10, 20], basicYieldReturn());
+ Assert.equals('123', dummy);
+ }
+
+ @:yield function basicYieldReturn():Iterator {
+ dummy += '1';
+ @:yield return 10;
+ dummy += '2';
+ @:yield return 20;
+ dummy += '3';
+ }
+
+ #if broken
+
+ public function testBasicYieldReturn_multipleIterations() {
+ var generator = basicYieldReturn();
+ assert([10, 20], generator);
+ Assert.equals('123', dummy);
+ assert([10, 20], generator);
+ Assert.equals('123', dummy);
+ }
+
+ #end
+
+ public function testBasicYieldBreak() {
+ assert([10], basicYieldBreak());
+ Assert.equals('12', dummy);
+ }
+
+ @:yield function basicYieldBreak() {
+ dummy += '1';
+ @:yield return 10;
+ dummy += '2';
+ return;
+ dummy += '3';
+ @:yield return 20;
+ dummy += '4';
+ }
+
+ public function testLocalVars() {
+ assert([10, 25, 40, 19, 30], localVars(10, 20, 30));
+ }
+
+ @:yield function localVars(a:Int, b:Int, a1:Int) {
+ var q = b;
+ @:yield return a;
+ var a = 5;
+ @:yield return a + q;
+ var q = q * 2;
+ @:yield return q;
+ for (a in 1...2) {
+ q = a * 10;
+ }
+ for (c in 1...2) {
+ q += 5;
+ }
+ for (i in 0...2) {
+ for (j in 0...2) {
+ q += i + j;
+ }
+ }
+ @:yield return q;
+ @:yield return a1;
+ }
+
+ public function testLocalVars_sameVarNameInTwoChildScopes() {
+ assert([10], localVars_sameVarNameInTwoChildScopes(true));
+ assert([20], localVars_sameVarNameInTwoChildScopes(false));
+ }
+
+ @:yield function localVars_sameVarNameInTwoChildScopes(condition:Bool) {
+ if (condition) {
+ var v = 10;
+ @:yield return v;
+ } else {
+ var v = 'ab';
+ @:yield return v.length * 10;
+ }
+ }
+
+ public function testLocalFunction() {
+ assert([10, 20, 30], localFunction());
+ }
+
+ @:yield function localFunction() {
+ inline function local1()
+ return 20;
+ function local2() {
+ return 30;
+ }
+ @:yield return 10;
+ @:yield return local1();
+ var value = local2();
+ @:yield return value;
+ }
+
+ public function testInheritance() {
+ var result = [for (it in descendantsOfParent()) it];
+ Assert.equals(2, result.length);
+ }
+
+ @:yield function descendantsOfParent():Iterator {
+ @:yield return new Child1();
+ @:yield return new Child2();
+ }
+}
+
+private class Parent {
+ public function new() {}
+}
+
+private class Child1 extends Parent {}
+private class Child2 extends Parent {}
+
+function main() {
+ utest.UTest.run([
+ new TestYieldBasic()
+ ]);
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/TestYieldClosure.hx b/tests/misc/coroutines/src/yield/TestYieldClosure.hx
new file mode 100644
index 00000000000..56183ea8f2d
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/TestYieldClosure.hx
@@ -0,0 +1,88 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldClosure extends BaseCase {
+ // @:yield function closure(arg) {
+ // var fn = @:yield function(arg2) {
+ // }
+ // @:yield function another(arg2) {
+ // trace({arg2:arg2});
+ // }
+ // }
+
+ var anchor:Dynamic;
+
+ public function testClosure() {
+ assert([20, 40, 60, 80, 20, 40, 60, 80, 100], closure(2));
+ Assert.equals('1234512345', dummy);
+ }
+
+ @:yield function closure(arg) {
+ var a:Dynamic = arg;
+ anchor = a;
+ var fn = @:yield function(arg2) {
+ var b:Dynamic = arg;
+ anchor = b;
+ dummy += '1';
+ @:yield return arg * 10;
+ dummy += '2';
+ @:yield return cast a * 20; // TODO: I had to insert these casts because this was errorring with Float should be Int
+ dummy += '3';
+ @:yield return cast b * 30;
+ dummy += '4';
+ @:yield return arg2 * 40;
+ dummy += '5';
+ }
+ for(i in fn(a)) {
+ @:yield return i;
+ }
+ @:yield function another(arg2) {
+ var b:Dynamic = arg;
+ anchor = b;
+ dummy += '1';
+ @:yield return arg * 10;
+ dummy += '2';
+ @:yield return cast a * 20;
+ dummy += '3';
+ @:yield return cast b * 30;
+ dummy += '4';
+ @:yield return arg2 * 40;
+ dummy += '5';
+ for(i in (@:yield function() @:yield return arg2 * 50)()) {
+ @:yield return i;
+ }
+ }
+ for(i in another(a)) {
+ @:yield return i;
+ }
+ }
+
+
+ public function testClosure_nested() {
+ assert([100], closure_nested(10));
+ }
+
+ @:yield function closure_nested(arg) {
+ @:yield function another(arg2) {
+ var fn = @:yield function() @:yield return arg2 * 10;
+ for(i in fn()) @:yield return i;
+ }
+ for(i in another(arg)) {
+ @:yield return i;
+ }
+ }
+
+
+ public function testClosure_withoutYield() {
+ assert([0, 10], closure_withoutYield(1));
+ }
+
+ @:yield function closure_withoutYield(arg:Int) {
+ var fn = function() return arg * 10;
+ for(i in 0...2) {
+ @:yield return fn() * i;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/TestYieldFor.hx b/tests/misc/coroutines/src/yield/TestYieldFor.hx
new file mode 100644
index 00000000000..3f1511c4108
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/TestYieldFor.hx
@@ -0,0 +1,86 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldFor extends BaseCase {
+
+ public function testFor_basicYieldReturn() {
+ assert([11, 21, 31], for_basicYieldReturn(1));
+ Assert.equals('01122334', dummy);
+ }
+
+ @:yield function for_basicYieldReturn(arg:Int) {
+ dummy += '0';
+ for(i in 1...4) {
+ dummy += i;
+ @:yield return i * 10 + arg;
+ dummy += i;
+ }
+ dummy += '4';
+ }
+
+ public function testFor_basicYieldBreak() {
+ assert([10], for_basicYieldBreak());
+ Assert.equals('012', dummy);
+ }
+
+ @:yield function for_basicYieldBreak() {
+ dummy += '0';
+ @:yield return 10;
+ dummy += '1';
+ for(i in 2...100) {
+ dummy += i;
+ return;
+ dummy += i;
+ }
+ dummy += '101';
+ }
+
+ public function testFor_nested() {
+ assert([0, 1, 10, 11], for_nested());
+ Assert.equals('0[><><][><><]2', dummy);
+ }
+
+ @:yield function for_nested() {
+ dummy += '0';
+ for(i in 0...2) {
+ dummy += '[';
+ for(j in 0...2) {
+ dummy += '>';
+ @:yield return i * 10 + j;
+ dummy += '<';
+ }
+ dummy += ']';
+ }
+ dummy += '2';
+ }
+
+
+ public function testFor_breakContinue() {
+ assert([0, -1, 2], for_breakContinue());
+ Assert.equals('12356789235235670', dummy);
+ }
+
+ @:yield function for_breakContinue() {
+ dummy += '1';
+ for(i in 0...10) {
+ dummy += '2';
+ while(true) {
+ dummy += '3';
+ break;
+ dummy += '4';
+ }
+ dummy += '5';
+ if(i == 1) continue;
+ dummy += '6';
+ @:yield return i;
+ dummy += '7';
+ if(i == 2) break;
+ dummy += '8';
+ @:yield return -1;
+ dummy += '9';
+ }
+ dummy += '0';
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/TestYieldIf.hx b/tests/misc/coroutines/src/yield/TestYieldIf.hx
new file mode 100644
index 00000000000..45dcf6be1a5
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/TestYieldIf.hx
@@ -0,0 +1,97 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldIf extends BaseCase {
+
+ public function testIf_withoutElse() {
+ assert([10, 20, 30, 40], ifWithoutElse(true));
+ Assert.equals('1234567', dummy);
+ assert([10, 30], ifWithoutElse(false));
+ Assert.equals('12567', dummy);
+ }
+
+ @:yield function ifWithoutElse(condition:Bool) {
+ dummy += '1';
+ @:yield return 10;
+ dummy += '2';
+ if(condition) {
+ dummy += '3';
+ @:yield return 20;
+ dummy += '4';
+ }
+ dummy += '5';
+ @:yield return 30;
+ dummy += '6';
+ if(condition) @:yield return 40;
+ dummy += '7';
+ }
+
+
+ public function testIfElse() {
+ assert([10], ifElse(true));
+ Assert.equals('123678', dummy);
+ assert([20, 30], ifElse(false));
+ Assert.equals('14568', dummy);
+ }
+
+ @:yield function ifElse(condition:Bool) {
+ dummy += '1';
+ if(condition) {
+ dummy += '2';
+ @:yield return 10;
+ dummy += '3';
+ } else {
+ dummy += '4';
+ @:yield return 20;
+ dummy += '5';
+ }
+ dummy += '6';
+ if(condition) {
+ dummy += '7';
+ } else @:yield return 30;
+ dummy += '8';
+ }
+
+ #if broken
+ public function testIfElse_withoutYield_runInSingleState() {
+ assert([10], ifElseNoYield(true));
+ assert([10], ifElseNoYield(false));
+ }
+
+ @:yield function ifElseNoYield(condition:Bool) {
+ var state = __ctx__.state; //__ctx__ is generated by build macros
+ if(condition) {
+ Assert.equals(state, __ctx__.state);
+ } else {
+ Assert.equals(state, __ctx__.state);
+ }
+ Assert.equals(state, __ctx__.state);
+
+ @:yield return 10;
+ }
+ #end
+
+
+ public function testIfElse_nestedIfs() {
+ assert([10], nestedIfs(true));
+ Assert.equals('123456', dummy);
+ assert([], nestedIfs(false));
+ Assert.equals('16', dummy);
+ }
+
+ @:yield function nestedIfs(condition:Bool) {
+ dummy += '1';
+ if(condition) {
+ dummy += '2';
+ if(condition) {
+ dummy += '3';
+ @:yield return 10;
+ dummy += '4';
+ }
+ dummy += '5';
+ }
+ dummy += '6';
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/TestYieldSwitch.hx b/tests/misc/coroutines/src/yield/TestYieldSwitch.hx
new file mode 100644
index 00000000000..efb51ce0690
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/TestYieldSwitch.hx
@@ -0,0 +1,105 @@
+package yield;
+
+import yield.Yield;
+
+private enum Example {
+ One;
+ Two(v:Int);
+ Three(v:String);
+ Four;
+}
+
+@:build(yield.YieldMacro.build())
+class TestYieldSwitch extends BaseCase {
+
+ public function testSwitch() {
+ assert([10, 30], basicSwitch(One));
+ Assert.equals('1230-', dummy);
+ assert([20, 30], basicSwitch(Two(20)));
+ Assert.equals('1450-', dummy);
+ assert([5, 30], basicSwitch(Three('hello')));
+ Assert.equals('1670-', dummy);
+ assert([30], basicSwitch(Three('h')));
+ Assert.equals('1h0-', dummy);
+ assert([], basicSwitch(Four));
+ Assert.equals('18', dummy);
+ }
+
+ @:yield function basicSwitch(arg) {
+ dummy += '1';
+ switch(arg) {
+ case One:
+ dummy += '2';
+ @:yield return 10;
+ dummy += '3';
+ case Two(v):
+ dummy += '4';
+ @:yield return v;
+ dummy += '5';
+ case Three(v) if(v.length > 1):
+ dummy += '6';
+ @:yield return v.length;
+ dummy += '7';
+ case Three(v):
+ dummy += v;
+ default:
+ dummy += '8';
+ return;
+ dummy += '9';
+ }
+ dummy += '0';
+ @:yield return 30;
+ dummy += '-';
+ }
+
+ #if broken
+ public function testSwitch_withoutYield() {
+ assert([30], switch_withoutYield(One));
+ assert([30], switch_withoutYield(Two(10)));
+ assert([30], switch_withoutYield(Three('hello')));
+ assert([30], switch_withoutYield(Four));
+ }
+
+ @:yield function switch_withoutYield(arg) {
+ var state = __ctx__.state;
+ switch(arg) {
+ case One: Assert.equals(state, __ctx__.state);
+ case Two(v): Assert.equals(state, __ctx__.state);
+ case Three(v): Assert.equals(state, __ctx__.state);
+ case _: Assert.equals(state, __ctx__.state);
+ }
+ Assert.equals(state, __ctx__.state);
+ @:yield return 30;
+ }
+ #end
+
+ public function testSwitch_multipleSwitch() {
+ assert([20, 30, 40], switch_multipleSwitch(One));
+ assert([10, 20, 40], switch_multipleSwitch(Two(999)));
+ }
+
+ @:yield function switch_multipleSwitch(arg) {
+ switch(arg) {
+ case Two(_): @:yield return 10;
+ case _:
+ }
+ @:yield return 20;
+ switch(arg) {
+ case One: @:yield return 30;
+ case _:
+ }
+ @:yield return 40;
+ }
+
+ public function testNoYieldSwitchAsArgument() {
+ assert([10], noYieldSwitchAsArgument(10));
+ }
+
+ @:yield function noYieldSwitchAsArgument(arg:Int) {
+ var fn = function(v:Int) return v;
+ var result = fn(switch(arg) {
+ case _: arg;
+ });
+ @:yield return result;
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/TestYieldWhile.hx b/tests/misc/coroutines/src/yield/TestYieldWhile.hx
new file mode 100644
index 00000000000..f1561956a40
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/TestYieldWhile.hx
@@ -0,0 +1,98 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldWhile extends BaseCase {
+
+ public function testWhile_basicYieldReturn() {
+ assert([11, 21, 31], while_basicYieldReturn(1));
+ Assert.equals('01122334', dummy);
+ }
+
+ @:yield function while_basicYieldReturn(arg:Int) {
+ dummy += '0';
+ var i = 1;
+ while(i < 4) {
+ dummy += i;
+ @:yield return i * 10 + arg;
+ dummy += i;
+ i++;
+ }
+ dummy += '4';
+ }
+
+
+ public function testWhile_basicYieldBreak() {
+ assert([10], while_basicYieldBreak());
+ Assert.equals('012', dummy);
+ }
+
+ @:yield function while_basicYieldBreak() {
+ dummy += '0';
+ @:yield return 10;
+ dummy += '1';
+ var i = 2;
+ while(i < 100) {
+ dummy += i;
+ return;
+ dummy += i;
+ i++;
+ }
+ dummy += '101';
+ }
+
+
+ public function testWhile_nested() {
+ assert([0, 1, 10, 11], while_nested());
+ Assert.equals('0[><><][><><]2', dummy);
+ }
+
+ @:yield function while_nested() {
+ dummy += '0';
+ var i = 0;
+ while(i < 2) {
+ dummy += '[';
+ var j = 0;
+ while(j < 2) {
+ dummy += '>';
+ @:yield return i * 10 + j;
+ dummy += '<';
+ j++;
+ }
+ dummy += ']';
+ i++;
+ }
+ dummy += '2';
+ }
+
+
+ public function testWhile_breakContinue() {
+ assert([0, -1, 2], while_breakContinue());
+ Assert.equals('12356789235235670', dummy);
+ }
+
+ @:yield function while_breakContinue() {
+ dummy += '1';
+ var i = -1;
+ while(i < 10) {
+ i++;
+ dummy += '2';
+ while(true) {
+ dummy += '3';
+ break;
+ dummy += '4';
+ }
+ dummy += '5';
+ if(i == 1) continue;
+ dummy += '6';
+ @:yield return i;
+ dummy += '7';
+ if(i == 2) break;
+ dummy += '8';
+ @:yield return -1;
+ dummy += '9';
+ }
+ dummy += '0';
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/coroutines/src/yield/Yield.hx b/tests/misc/coroutines/src/yield/Yield.hx
new file mode 100644
index 00000000000..7ba3990cfdf
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/Yield.hx
@@ -0,0 +1,39 @@
+package yield;
+import haxe.coro.Coroutine;
+
+typedef Yield = CoroutineVoid>;
+
+function sequence(f:Coroutine->Void>):Iterator {
+ var finished = false;
+ var nextValue:T = null;
+
+ var nextStep = null;
+
+ function finish(_, err) {
+ if (err != null) {
+ throw err;
+ }
+ finished = true;
+ }
+
+ @:coroutine function yield(value:T) {
+ nextValue = value;
+ Coroutine.suspend(cont -> nextStep = cont);
+ }
+
+ function hasNext():Bool {
+ if (nextStep == null) {
+ nextStep = f.create(yield, finish);
+ nextStep(null, Normal);
+ }
+ return !finished;
+ }
+
+ function next():T {
+ var value = nextValue;
+ nextStep(null, Normal);
+ return value;
+ }
+
+ return {hasNext: hasNext, next: next};
+}
diff --git a/tests/misc/coroutines/src/yield/YieldMacro.hx b/tests/misc/coroutines/src/yield/YieldMacro.hx
new file mode 100644
index 00000000000..4e86d53d65b
--- /dev/null
+++ b/tests/misc/coroutines/src/yield/YieldMacro.hx
@@ -0,0 +1,61 @@
+package yield;
+
+import haxe.macro.Context;
+import haxe.macro.Expr;
+import haxe.macro.Printer;
+using Lambda;
+using haxe.macro.Tools;
+
+class YieldMacro {
+ macro static public function build():Array {
+ var yieldFunctions = [];
+ var otherFunctions = [];
+ var inputFields = Context.getBuildFields();
+ for (field in inputFields) {
+ if (field.meta.exists(meta -> meta.name == ":yield")) {
+ var f = switch (field.kind) {
+ case FFun(f):
+ f;
+ case _:
+ Context.error("@:yield fields should be functions, found " + field.kind, field.pos);
+ }
+ transformYieldFunction(f, field.pos);
+ yieldFunctions.push(field);
+ }
+ }
+ return inputFields;
+ }
+
+ static function transformYieldFunction(f:Function, p:Position) {
+ if (f.expr == null) {
+ Context.error('@:yield function has no expression', p);
+ }
+ var ret = switch (f.ret) {
+ case macro :Iterator<$ct>:
+ macro : Coroutine<$ct -> Void>;
+ case _:
+ null;
+ }
+ function mapYield(e:Expr) {
+ return switch (e) {
+ case macro @:yield return $e:
+ e = mapYield(e);
+ macro @:pos(e.pos) yield($e);
+ case macro @:yield $e:
+ switch (e.expr) {
+ case EFunction(kind, f):
+ transformYieldFunction(f, e.pos);
+ e;
+ case _:
+ e.map(mapYield);
+ }
+ case _:
+ e.map(mapYield);
+ }
+ }
+ var e = mapYield(f.expr);
+ e = macro return sequence((yield : $ret) -> $e);
+ // trace(new Printer().printExpr(e));
+ f.expr = e;
+ }
+}
\ No newline at end of file
diff --git a/tests/misc/projects/Issue10871/Context/compile.hxml b/tests/misc/projects/Issue10871/Context/compile.hxml.disabled
similarity index 100%
rename from tests/misc/projects/Issue10871/Context/compile.hxml
rename to tests/misc/projects/Issue10871/Context/compile.hxml.disabled
diff --git a/tests/misc/projects/coro/unbound-type-params/Main.hx b/tests/misc/projects/coro/unbound-type-params/Main.hx
new file mode 100644
index 00000000000..f74594fcdd3
--- /dev/null
+++ b/tests/misc/projects/coro/unbound-type-params/Main.hx
@@ -0,0 +1,24 @@
+import haxe.coro.Coroutine;
+
+class C {
+ public function new() {}
+
+ public function test() {
+ @:coroutine function f():{tc:TC, tf:TF, tl:TL} {
+ return null;
+ }
+
+ Coroutine.run(f);
+ }
+
+ @:coroutine public function coro():{tc: TC, tf:TF} {
+ return null;
+ }
+}
+
+function main() {
+ var c = new C();
+ c.test();
+
+ Coroutine.run(c.coro);
+}
\ No newline at end of file
diff --git a/tests/misc/projects/coro/unbound-type-params/compile.hxml b/tests/misc/projects/coro/unbound-type-params/compile.hxml
new file mode 100644
index 00000000000..c561a642308
--- /dev/null
+++ b/tests/misc/projects/coro/unbound-type-params/compile.hxml
@@ -0,0 +1,2 @@
+--main Main
+--hxb bin/out.hxb
\ No newline at end of file
diff --git a/tests/misc/projects/coro/unbound-type-params/compile.hxml.stderr b/tests/misc/projects/coro/unbound-type-params/compile.hxml.stderr
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/runci/targets/Cpp.hx b/tests/runci/targets/Cpp.hx
index c56246f563a..f9dbb58e21f 100644
--- a/tests/runci/targets/Cpp.hx
+++ b/tests/runci/targets/Cpp.hx
@@ -72,6 +72,10 @@ class Cpp {
runCpp("bin/cppia/Host-debug", ["bin/unit.cppia", "-jit"]);
}
+ runci.tests.CoroutineTests.run(["build-cpp.hxml"], args ->
+ runCpp("bin/cpp/Main-debug")
+ );
+
Display.maybeRunDisplayTests(Cpp);
changeDirectory(sysDir);
diff --git a/tests/runci/targets/Hl.hx b/tests/runci/targets/Hl.hx
index 950fbbc91d0..e6ef6240dab 100644
--- a/tests/runci/targets/Hl.hx
+++ b/tests/runci/targets/Hl.hx
@@ -139,6 +139,8 @@ class Hl {
runCommand("haxe", ["compile-hlc.hxml", "--undefine", "analyzer-optimize"].concat(args));
buildAndRunHlc("bin/hlc", "unit", runCommand);
+ runci.tests.CoroutineTests.run(["build-hl.hxml"]);
+
changeDirectory(threadsDir);
buildAndRun("build.hxml", "export/threads");
diff --git a/tests/runci/targets/Js.hx b/tests/runci/targets/Js.hx
index 815b29e2ebf..f620b523008 100644
--- a/tests/runci/targets/Js.hx
+++ b/tests/runci/targets/Js.hx
@@ -76,6 +76,8 @@ class Js {
changeDirectory(getMiscSubDir("es6"));
runCommand("haxe", ["run.hxml"]);
+ runci.tests.CoroutineTests.run(["build-js.hxml"]);
+
haxelibInstallGit("HaxeFoundation", "hxnodejs");
final env = Sys.environment();
if (
@@ -123,13 +125,13 @@ class Js {
runci.targets.Jvm.getJavaDependencies(); // this is awkward
haxelibInstallGit("Simn", "haxeserver");
- changeDirectory(serverDir);
- runCommand("haxe", ["build.hxml"]);
- runCommand("node", ["test.js"]);
- runCommand("haxe", ["build.hxml", "-D", "disable-hxb-optimizations"]);
- runCommand("node", ["test.js"]);
- runCommand("haxe", ["build.hxml", "-D", "disable-hxb-cache"]);
- runCommand("node", ["test.js"]);
+ // changeDirectory(serverDir);
+ // runCommand("haxe", ["build.hxml"]);
+ // runCommand("node", ["test.js"]);
+ // runCommand("haxe", ["build.hxml", "-D", "disable-hxb-optimizations"]);
+ // runCommand("node", ["test.js"]);
+ // runCommand("haxe", ["build.hxml", "-D", "disable-hxb-cache"]);
+ // runCommand("node", ["test.js"]);
Display.maybeRunDisplayTests(Js);
diff --git a/tests/runci/targets/Jvm.hx b/tests/runci/targets/Jvm.hx
index 6f91a72cc03..45c399ca765 100644
--- a/tests/runci/targets/Jvm.hx
+++ b/tests/runci/targets/Jvm.hx
@@ -33,6 +33,10 @@ class Jvm {
runCommand("java", ["-jar", "bin/unit.jar"]);
}
+ runci.tests.CoroutineTests.run(["build-jvm.hxml", "--hxb", "bin/coro.hxb"], args ->
+ runCommand("haxe", args.concat(["--hxb-lib", "bin/coro.hxb"]))
+ );
+
Display.maybeRunDisplayTests(Jvm);
changeDirectory(miscJavaDir);
diff --git a/tests/runci/targets/Lua.hx b/tests/runci/targets/Lua.hx
index 525362a5074..803a3377046 100644
--- a/tests/runci/targets/Lua.hx
+++ b/tests/runci/targets/Lua.hx
@@ -96,6 +96,8 @@ class Lua {
runCommand("haxe", ["compile-lua.hxml"].concat(args).concat(luaVer));
runCommand("lua", ["bin/unit.lua"]);
+ runci.tests.CoroutineTests.run(["build-lua.hxml"]);
+
Display.maybeRunDisplayTests(Lua);
changeDirectory(sysDir);
diff --git a/tests/runci/targets/Macro.hx b/tests/runci/targets/Macro.hx
index a2c2f073347..0e9a5d26be0 100644
--- a/tests/runci/targets/Macro.hx
+++ b/tests/runci/targets/Macro.hx
@@ -8,6 +8,8 @@ class Macro {
runCommand("haxe", ["compile-macro.hxml", "--hxb", "bin/hxb/eval.zip"].concat(args));
runCommand("haxe", ["compile-macro.hxml", "--hxb-lib", "bin/hxb/eval.zip"].concat(args));
+ runci.tests.CoroutineTests.run(["build-eval.hxml"]);
+
changeDirectory(displayDir);
haxelibInstallGit("Simn", "haxeserver");
diff --git a/tests/runci/targets/Neko.hx b/tests/runci/targets/Neko.hx
index e27192cdc64..c82f7710d60 100644
--- a/tests/runci/targets/Neko.hx
+++ b/tests/runci/targets/Neko.hx
@@ -8,6 +8,8 @@ class Neko {
runCommand("haxe", ["compile-neko.hxml", "-D", "dump", "-D", "dump_ignore_var_ids"].concat(args));
runCommand("neko", ["bin/unit.n"]);
+ runci.tests.CoroutineTests.run(["build-neko.hxml"]);
+
changeDirectory(getMiscSubDir('neko'));
runCommand("haxe", ["run.hxml"].concat(args));
diff --git a/tests/runci/targets/Php.hx b/tests/runci/targets/Php.hx
index 1390d5205f9..cec6fdc3968 100644
--- a/tests/runci/targets/Php.hx
+++ b/tests/runci/targets/Php.hx
@@ -87,6 +87,8 @@ class Php {
runCommand("haxe", ["compile-php.hxml"].concat(prefix).concat(args));
runCommand("php", generateArgs(binDir + "/index.php"));
+ runci.tests.CoroutineTests.run(["build-php.hxml"]);
+
Display.maybeRunDisplayTests(Php);
changeDirectory(sysDir);
diff --git a/tests/runci/targets/Python.hx b/tests/runci/targets/Python.hx
index 3caf4443515..3540e80d33b 100644
--- a/tests/runci/targets/Python.hx
+++ b/tests/runci/targets/Python.hx
@@ -67,6 +67,8 @@ class Python {
runCommand(py, ["bin/unit34.py"]);
}
+ runci.tests.CoroutineTests.run(["build-python.hxml"]);
+
Display.maybeRunDisplayTests(Python);
changeDirectory(sysDir);
diff --git a/tests/runci/tests/CoroutineTests.hx b/tests/runci/tests/CoroutineTests.hx
new file mode 100644
index 00000000000..5bb00dce3bb
--- /dev/null
+++ b/tests/runci/tests/CoroutineTests.hx
@@ -0,0 +1,21 @@
+package runci.tests;
+
+import runci.System.*;
+import runci.Config.*;
+
+class CoroutineTests {
+ static public function run(baseArgs:Array, ?afterwards:(args:Array) -> Void) {
+ infoMsg("Test coroutines:");
+ changeDirectory(getMiscSubDir("coroutines"));
+ for (opt in [[], ["-D", "coroutine.noopt"]]) {
+ for (thro in [[], ["-D", "coroutine.throw"]]) {
+ var args = baseArgs.concat(opt).concat(thro);
+ infoMsg("Running " + args.join(" "));
+ runCommand("haxe", args);
+ if (afterwards != null) {
+ afterwards(args);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/server/src/cases/ReplaceRanges.hx b/tests/server/src/cases/ReplaceRanges.hx
index 0a1aee0e2f9..fc32f84fb15 100644
--- a/tests/server/src/cases/ReplaceRanges.hx
+++ b/tests/server/src/cases/ReplaceRanges.hx
@@ -7,13 +7,13 @@ import utest.Assert.*;
// TODO: somebody has to clean this up
class ReplaceRanges extends TestCase {
- function complete(content:String, markerIndex:Int, cb:(response:CompletionResponse, markers:Map) -> Void) {
+ @:coroutine
+ function complete(content:String, markerIndex:Int) {
var transform = Marker.extractMarkers(content);
vfs.putContent("Main.hx", transform.source);
- runHaxeJson([], DisplayMethods.Completion, {file: new FsPath("Main.hx"), offset: transform.markers[markerIndex], wasAutoTriggered: true}, function() {
- var result = parseCompletion();
- cb(result.result, transform.markers);
- });
+ runHaxeJson([], DisplayMethods.Completion, {file: new FsPath("Main.hx"), offset: transform.markers[markerIndex], wasAutoTriggered: true});
+ var result = parseCompletion();
+ return {response: result.result, markers: transform.markers};
}
function checkReplaceRange(markers:Map, startIndex:Int, endIndex:Int, response:CompletionResponse, ?p:PosInfos) {
@@ -22,184 +22,184 @@ class ReplaceRanges extends TestCase {
}
function testType() {
- complete("{-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("{-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("{-1-}cl{-2-}", 2);
- equals("cl", response.filterString);
- checkReplaceRange(markers, 1, 2, response);
+ var result = complete("{-1-}cl{-2-}", 2);
+ equals("cl", result.response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
}
function testModifier() {
- complete("extern {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("extern {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("extern {-1-}cl{-2-}", 2);
- equals("cl", response.filterString);
- checkReplaceRange(markers, 1, 2, response);
+ var result = complete("extern {-1-}cl{-2-}", 2);
+ equals("cl", result.response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
}
function testExtends() {
- complete("class C extends {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("class C extends {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("class C extends {-1-}Cl{-2-}", 2);
- equals("Cl", response.filterString);
- checkReplaceRange(markers, 1, 2, response);
+ var result = complete("class C extends {-1-}Cl{-2-}", 2);
+ equals("Cl", result.response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
- complete("class C {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("class C {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("class C {-1-}ex{-2-}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("ex", response.filterString);
+ var result = complete("class C {-1-}ex{-2-}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("ex", result.response.filterString);
- complete("class C {-1-}ext{-2-} {}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("ext", response.filterString);
+ var result = complete("class C {-1-}ext{-2-} {}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("ext", result.response.filterString);
}
function testImplements() {
- complete("class C implements {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("class C implements {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("class C implements {-1-}Cl{-2-}", 2);
- equals("Cl", response.filterString);
- checkReplaceRange(markers, 1, 2, response);
+ var result = complete("class C implements {-1-}Cl{-2-}", 2);
+ equals("Cl", result.response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
- complete("class C {-1-} {}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("class C {-1-} {}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("class C {-1-}impl{-2-} {}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("impl", response.filterString);
+ var result = complete("class C {-1-}impl{-2-} {}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("impl", result.response.filterString);
}
function testImport() {
- complete("import {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("import {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("import {-1-}Cl{-2-}", 2);
- // equals("Cl", response.filterString);
- checkReplaceRange(markers, 1, 2, response);
+ var result = complete("import {-1-}Cl{-2-}", 2);
+ // equals("Cl", result.response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
}
function testUsing() {
- complete("using {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("using {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("using {-1-}Cl{-2-}", 2);
- // equals("Cl", response.filterString);
- checkReplaceRange(markers, 1, 2, response);
+ var result = complete("using {-1-}Cl{-2-}", 2);
+ // equals("Cl", result.response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
}
function testTo() {
- complete("abstract A(String) to {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("abstract A(String) to {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("abstract A(String) to {-1-} { }", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("abstract A(String) to {-1-} { }", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("abstract A(String) to {-1-}Cl{-2-}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("abstract A(String) to {-1-}Cl{-2-}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
- complete("abstract A(String) to {-1-}Cl{-2-} { }", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("abstract A(String) to {-1-}Cl{-2-} { }", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
}
function testFrom() {
- complete("abstract A(String) from {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("abstract A(String) from {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("abstract A(String) from {-1-} { }", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("abstract A(String) from {-1-} { }", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("abstract A(String) from {-1-}Cl{-2-}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("abstract A(String) from {-1-}Cl{-2-}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
- complete("abstract A(String) from {-1-}Cl{-2-} { }", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("abstract A(String) from {-1-}Cl{-2-} { }", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
}
function testStructuralExtension() {
- complete("typedef Main = { } & {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("typedef Main = { } & {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("typedef Main = { } & {-1-}Cl{-2-}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("typedef Main = { } & {-1-}Cl{-2-}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
- complete("typedef Main = { > {-1-}", 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete("typedef Main = { > {-1-}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete("typedef Main = { > {-1-}Cl{-2-}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("typedef Main = { > {-1-}Cl{-2-}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
}
function testFields() {
- complete('class Main { static function main() "".{-1-}', 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete('class Main { static function main() "".{-1-}', 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete('class Main { static function main() "".{-1-}char', 1);
- checkReplaceRange(markers, 1, 1, response);
+ var result = complete('class Main { static function main() "".{-1-}char', 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
- complete('class Main { static function main() "".{-1-}char{-2-}', 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("char", response.filterString);
+ var result = complete('class Main { static function main() "".{-1-}char{-2-}', 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("char", result.response.filterString);
}
function testOverride() {
- complete("import haxe.io.Bytes; class Main extends Bytes { static function main() { } override {-1-}}", 1);
- checkReplaceRange(markers, 1, 1, response);
- equals("", response.filterString);
+ var result = complete("import haxe.io.Bytes; class Main extends Bytes { static function main() { } override {-1-}}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
+ equals("", result.response.filterString);
- complete("import haxe.io.Bytes; class Main extends Bytes { static function main() { } override {-1-}get{-2-}}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("get", response.filterString);
+ var result = complete("import haxe.io.Bytes; class Main extends Bytes { static function main() { } override {-1-}get{-2-}}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("get", result.response.filterString);
}
function testTypedef() {
- complete("typedef Foo = {-1-}
+ var result = complete("typedef Foo = {-1-}
", 1);
- checkReplaceRange(markers, 1, 1, response);
- equals("", response.filterString);
+ checkReplaceRange(result.markers, 1, 1, result.response);
+ equals("", result.response.filterString);
- complete("typedef Foo = {-1-}Cl{-2-}
+ var result = complete("typedef Foo = {-1-}Cl{-2-}
", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
}
function testTypehint() {
- complete("class Main { static function main() { var t:{-1-} }}", 1);
- checkReplaceRange(markers, 1, 1, response);
- equals("", response.filterString);
+ var result = complete("class Main { static function main() { var t:{-1-} }}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
+ equals("", result.response.filterString);
- complete("class Main { static function main() { var t:{-1-}Cl{-2-} }}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("class Main { static function main() { var t:{-1-}Cl{-2-} }}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
- complete("class Main { static function main() { var t:{-1-}String{-2-} }}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("String", response.filterString);
+ var result = complete("class Main { static function main() { var t:{-1-}String{-2-} }}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("String", result.response.filterString);
- complete("class Main { static function main() { var t:{-1-}Str{-2-}ing }}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Str", response.filterString);
+ var result = complete("class Main { static function main() { var t:{-1-}Str{-2-}ing }}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Str", result.response.filterString);
}
function testTypeParameter() {
- complete("class Main { static function main() { var t:{-1-} }}", 1);
- checkReplaceRange(markers, 1, 1, response);
- equals("", response.filterString);
+ var result = complete("class Main { static function main() { var t:{-1-} }}", 1);
+ checkReplaceRange(result.markers, 1, 1, result.response);
+ equals("", result.response.filterString);
- complete("class Main { static function main() { var t:{-1-}Cl{-2-} }}", 2);
- checkReplaceRange(markers, 1, 2, response);
- equals("Cl", response.filterString);
+ var result = complete("class Main { static function main() { var t:{-1-}Cl{-2-} }}", 2);
+ checkReplaceRange(result.markers, 1, 2, result.response);
+ equals("Cl", result.response.filterString);
}
}
diff --git a/tests/server/src/cases/ServerTests.hx b/tests/server/src/cases/ServerTests.hx
index 7350541fc54..ee28e34ab40 100644
--- a/tests/server/src/cases/ServerTests.hx
+++ b/tests/server/src/cases/ServerTests.hx
@@ -590,23 +590,18 @@ class ServerTests extends TestCase {
assertSuccess();
}
- @:async function testStackOverflow(async:utest.Async) {
+ function testStackOverflow() {
vfs.putContent("Empty.hx", getTemplate("Empty.hx"));
var args = ["-main", "Empty.hx", "--macro", "allowPackage('sys')", "--interp", "--no-output"];
var runs = 0;
- function runLoop() {
- runHaxeJson(args, DisplayMethods.Diagnostics, {file: new FsPath("Empty.hx")}, () -> {
- runHaxe(args.concat(["-D", "compile-only-define"]), () -> {
- if (assertSuccess() && ++runs < 20)
- runLoop();
- else
- async.done();
- });
- });
+ @:coroutine function runLoop() {
+ runHaxeJson(args, DisplayMethods.Diagnostics, {file: new FsPath("Empty.hx")});
+ runHaxe(args.concat(["-D", "compile-only-define"]));
+ if (assertSuccess() && ++runs < 20)
+ runLoop();
}
- async.setTimeout(20000);
runLoop();
}
diff --git a/tests/server/src/utils/macro/TestBuilder.macro.hx b/tests/server/src/utils/macro/TestBuilder.macro.hx
index 383c8fe70fa..f916a1aba66 100644
--- a/tests/server/src/utils/macro/TestBuilder.macro.hx
+++ b/tests/server/src/utils/macro/TestBuilder.macro.hx
@@ -22,12 +22,12 @@ class TestBuilder {
case FFun(f):
var variants = field.meta.filter(m -> m.name == ":variant");
if (variants.length == 0) {
- makeAsyncTest(f, field.pos);
+ makeAsyncTest(field);
} else {
// TODO: support functions that define their own async arg (not named `_` or `async`)
var args = f.args.copy();
f.args = [];
- makeAsyncTest(f, field.pos);
+ makeAsyncTest(field);
// Ignore original field; generate variants instead
removedFields.push(field);
@@ -88,80 +88,8 @@ class TestBuilder {
return fields.concat(newFields);
}
- static function makeAsyncTest(f:Function, fpos:Position) {
- var asyncName = switch f.args {
- case []:
- var name = "async";
- f.args.push({
- name: name,
- type: macro:utest.Async
- });
- name;
- case [arg]:
- if (arg.name == "_") {
- arg.name = "async";
- arg.type = macro:utest.Async;
- }
- arg.name;
- case _:
- Context.fatalError('Unexpected amount of test arguments', fpos);
- "";
- }
- switch (f.expr.expr) {
- case EBlock(el):
- var posInfos = Context.getPosInfos(f.expr.pos);
- var pos = Context.makePosition({min: posInfos.max, max: posInfos.max, file: posInfos.file});
- el.push(macro @:pos(pos) {
- if ($i{asyncName}.timedOut) Assert.fail("timeout");
- else $i{asyncName}.done();
- });
- f.expr = macro {
- $i{asyncName}.setTimeout(20000);
- ${transformHaxeCalls(asyncName, el)};
- }
- case _:
- Context.error("Block expression expected", f.expr.pos);
- }
- }
-
- static function transformHaxeCalls(asyncName:String, el:Array) {
- var e0 = el.shift();
- if (el.length == 0) {
- return e0;
- } else {
- var e = switch e0 {
- case macro runHaxe($a{args}):
- var e = transformHaxeCalls(asyncName, el);
- args.push(macro() -> ${failOnException(asyncName, e)});
- macro runHaxe($a{args});
- case macro runHaxeJson($a{args}):
- var e = transformHaxeCalls(asyncName, el);
- args.push(macro() -> ${failOnException(asyncName, e)});
- macro runHaxeJson($a{args});
- case macro runHaxeJsonCb($a{args}):
- var e = transformHaxeCalls(asyncName, el);
- args.push(macro() -> ${failOnException(asyncName, e)});
- macro runHaxeJsonCb($a{args});
- case macro complete($a{args}):
- var e = transformHaxeCalls(asyncName, el);
- args.push(macro function(response, markers) ${failOnException(asyncName, e)});
- macro complete($a{args});
- case _:
- macro {$e0; ${transformHaxeCalls(asyncName, el)}};
- }
- e.pos = e0.pos;
- return e;
- }
- }
-
- static function failOnException(asyncName:String, e:Expr):Expr {
- return macro
- @:pos(e.pos) try {
- $e;
- } catch (e) {
- Assert.fail(e.details());
- $i{asyncName}.done();
- return;
- }
+ static function makeAsyncTest(field:Field) {
+ field.meta.push({name: ":coroutine", params: [], pos: field.pos});
+ field.meta.push({name: ":timeout", params: [macro 20000], pos: field.pos});
}
}