From 60180e68352cac6b654c2ce88b4b2c9d8721cba6 Mon Sep 17 00:00:00 2001 From: Rudy Ges Date: Wed, 10 Apr 2024 18:50:34 +0200 Subject: [PATCH 1/2] [macro] add @:buildOrder(Early|Late) for build macros --- src-json/meta.json | 7 ++++ src/typing/typeloadFields.ml | 71 ++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src-json/meta.json b/src-json/meta.json index aa231387571..09df0fabdc8 100644 --- a/src-json/meta.json +++ b/src-json/meta.json @@ -91,6 +91,13 @@ "targets": ["TClass", "TEnum", "TAbstract"], "links": ["https://haxe.org/manual/macro-type-building.html"] }, + { + "name": "BuildOrder", + "metadata": ":buildOrder", + "doc": "Specify that a build macro should run Early or Late (in relation to other build macros for current type)", + "params": ["Build order"], + "targets": ["TClassField"] + }, { "name": "BuildXml", "metadata": ":buildXml", diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml index 92f67fef2e6..b3b9d9cc54f 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -382,34 +382,51 @@ let build_module_def ctx mt meta fvars fbuild = | TTypeDecl _ -> true, t_infos mt | _ -> false, t_infos mt in - let loop f_build = function - | Meta.Build,args,p when not is_typedef -> (fun () -> - let epath, el = (match args with - | [ECall (epath,el),p] -> epath, el - | _ -> raise_typing_error "Invalid build parameters" p - ) in - let cpath, meth = - let sl = try string_list_of_expr_path_raise ~root_cb:(resolve_type_import ctx p) epath with Exit -> raise_typing_error "Build call parameter must be a class path" p in - match sl with - | meth :: name :: pack -> - (List.rev pack,name), meth - | _ -> - raise_typing_error "Invalid macro path" p - in - if ctx.com.is_macro_context then raise_typing_error "You cannot use @:build inside a macro : make sure that your type is not used in macro" p; - let old = ctx.c.get_build_infos in - ctx.c.get_build_infos <- (fun() -> Some (mt, extract_param_types ti.mt_params, fvars())); - let r = try ctx.g.do_macro ctx MBuild cpath meth el p with e -> ctx.c.get_build_infos <- old; raise e in - ctx.c.get_build_infos <- old; - (match r with - | MError | MMacroInMacro -> raise_typing_error "Build failure" p - | MSuccess e -> fbuild e) - ) :: f_build - | _ -> - f_build + let get_build_priority meta = + let rec loop meta = match meta with + | [] -> 0 + | (Meta.BuildOrder,params,p) :: _ -> + (match params with + | [EConst (Ident "Early"),p] -> 1 + | [EConst (Ident "Late"),p] -> -1 + | [] -> raise_typing_error "Missing value for @:buildOrder" p + | _ -> raise_typing_error "Invalid @:buildOrder value. Expected `Early` or `Late`" p + ) + | _ :: meta -> loop meta + in + loop meta in + let build_macros = ExtLib.List.filter_map (fun m -> match m with + | Meta.Build,args,p when not is_typedef -> + let epath, el = (match args with + | [ECall (epath,el),p] -> epath, el + | _ -> raise_typing_error "Invalid build parameters" p + ) in + let cpath, meth = + let sl = try string_list_of_expr_path_raise ~root_cb:(resolve_type_import ctx p) epath with Exit -> raise_typing_error "Build call parameter must be a class path" p in + match sl with + | meth :: name :: pack -> (List.rev pack,name), meth + | _ -> raise_typing_error "Invalid macro path" p + in + let (_,_,_,cf) = ctx.g.do_load_macro ctx false cpath meth p in + let priority = get_build_priority cf.cf_meta in + Some (cpath, meth, el, priority, p) + | _ -> + None + ) meta in + let build_macros = List.sort (fun (_,_,_,p1,_) (_,_,_,p2,_) -> p2 - p1) build_macros in + let loop (cpath,meth,el,_,p) = (fun () -> + if ctx.com.is_macro_context then raise_typing_error "You cannot use @:build inside a macro : make sure that your type is not used in macro" p; + let old = ctx.c.get_build_infos in + ctx.c.get_build_infos <- (fun() -> Some (mt, extract_param_types ti.mt_params, fvars())); + let r = try ctx.g.do_macro ctx MBuild cpath meth el p with e -> ctx.c.get_build_infos <- old; raise e in + ctx.c.get_build_infos <- old; + (match r with + | MError | MMacroInMacro -> raise_typing_error "Build failure" p + | MSuccess e -> fbuild e) + ) in (* let errors go through to prevent resume if build fails *) - let f_build = List.fold_left loop [] meta in + let f_build = List.map loop build_macros in let f_enum = match mt with | TClassDecl ({cl_kind = KAbstractImpl a} as c) when a.a_enum -> Some (fun () -> @@ -429,7 +446,7 @@ let build_module_def ctx mt meta fvars fbuild = | _ -> None in - List.iter (fun f -> f()) (List.rev f_build); + List.iter (fun f -> f()) f_build; let apply_using = function | Meta.Using,el,p -> List.iter (fun e -> From 3667dae8696c22ef25cc8ba5249edbc6908bbb07 Mon Sep 17 00:00:00 2001 From: Rudy Ges Date: Wed, 10 Apr 2024 18:51:03 +0200 Subject: [PATCH 2/2] [tests] add tests for @:buildOrder(Late) and @:buildOrder(Early) --- .../misc/projects/Issue11582/Macro2.macro.hx | 72 +++++++++++++++++++ .../misc/projects/Issue11582/Macro3.macro.hx | 72 +++++++++++++++++++ tests/misc/projects/Issue11582/Main2.hx | 25 +++++++ tests/misc/projects/Issue11582/Main3.hx | 25 +++++++ .../projects/Issue11582/compile-early.hxml | 2 + .../Issue11582/compile-early.hxml.stdout | 2 + .../projects/Issue11582/compile-late.hxml | 2 + .../Issue11582/compile-late.hxml.stdout | 2 + 8 files changed, 202 insertions(+) create mode 100644 tests/misc/projects/Issue11582/Macro2.macro.hx create mode 100644 tests/misc/projects/Issue11582/Macro3.macro.hx create mode 100644 tests/misc/projects/Issue11582/Main2.hx create mode 100644 tests/misc/projects/Issue11582/Main3.hx create mode 100644 tests/misc/projects/Issue11582/compile-early.hxml create mode 100644 tests/misc/projects/Issue11582/compile-early.hxml.stdout create mode 100644 tests/misc/projects/Issue11582/compile-late.hxml create mode 100644 tests/misc/projects/Issue11582/compile-late.hxml.stdout diff --git a/tests/misc/projects/Issue11582/Macro2.macro.hx b/tests/misc/projects/Issue11582/Macro2.macro.hx new file mode 100644 index 00000000000..71b46b7a989 --- /dev/null +++ b/tests/misc/projects/Issue11582/Macro2.macro.hx @@ -0,0 +1,72 @@ +import haxe.macro.Context; +import haxe.macro.Expr; + +using StringTools; + +class Macro2 { + static var id = 0; + + static function registerBuild(i:String, fields:Array) { + if (Context.getLocalClass().get().isInterface) return null; + + var hasMacros = false; + for (f in fields) { + if (f.name == "macros") { + hasMacros = true; + break; + } + } + + if (!hasMacros) + fields = (macro class A { + public static var macros = []; + }).fields.concat(fields); + + var id = '_' + id++; + fields.push((macro class A { + static var $id = {macros.push($v{i}); 0;}; + }).fields[0]); + + return fields; + } + + static function isAsync(f:Field):Bool { + return Lambda.exists(f.meta, m -> m.name == ":async"); + } + + @:buildOrder(Late) + public static function buildTest() { + var fields = haxe.macro.Context.getBuildFields(); + var asyncArg = {name: "async", type: macro :Async}; + + // Add `async` arg to tests with `@:async` metadata + for (f in fields) { + if (!f.name.startsWith("test")) continue; + + switch f.kind { + case FFun({args: [], ret: ret, expr: expr, params: []}) if (isAsync(f)): + f.kind = FFun({args: [asyncArg], ret: ret, expr: expr, params: []}); + + case _: + } + } + + return registerBuild("Base Test", fields); + } + + public static function autoAsync() { + var fields = haxe.macro.Context.getBuildFields(); + + // Add `@:async` to all tests + for (f in fields) { + if (!f.name.startsWith("test")) continue; + + switch f.kind { + case FFun(_): f.meta.push({name: ":async", params: [], pos: f.pos}); + case _: + } + } + + return registerBuild("Auto async", fields); + } +} diff --git a/tests/misc/projects/Issue11582/Macro3.macro.hx b/tests/misc/projects/Issue11582/Macro3.macro.hx new file mode 100644 index 00000000000..6b47ba6768a --- /dev/null +++ b/tests/misc/projects/Issue11582/Macro3.macro.hx @@ -0,0 +1,72 @@ +import haxe.macro.Context; +import haxe.macro.Expr; + +using StringTools; + +class Macro3 { + static var id = 0; + + static function registerBuild(i:String, fields:Array) { + if (Context.getLocalClass().get().isInterface) return null; + + var hasMacros = false; + for (f in fields) { + if (f.name == "macros") { + hasMacros = true; + break; + } + } + + if (!hasMacros) + fields = (macro class A { + public static var macros = []; + }).fields.concat(fields); + + var id = '_' + id++; + fields.push((macro class A { + static var $id = {macros.push($v{i}); 0;}; + }).fields[0]); + + return fields; + } + + static function isAsync(f:Field):Bool { + return Lambda.exists(f.meta, m -> m.name == ":async"); + } + + public static function buildTest() { + var fields = haxe.macro.Context.getBuildFields(); + var asyncArg = {name: "async", type: macro :Async}; + + // Add `async` arg to tests with `@:async` metadata + for (f in fields) { + if (!f.name.startsWith("test")) continue; + + switch f.kind { + case FFun({args: [], ret: ret, expr: expr, params: []}) if (isAsync(f)): + f.kind = FFun({args: [asyncArg], ret: ret, expr: expr, params: []}); + + case _: + } + } + + return registerBuild("Base Test", fields); + } + + @:buildOrder(Early) + public static function autoAsync() { + var fields = haxe.macro.Context.getBuildFields(); + + // Add `@:async` to all tests + for (f in fields) { + if (!f.name.startsWith("test")) continue; + + switch f.kind { + case FFun(_): f.meta.push({name: ":async", params: [], pos: f.pos}); + case _: + } + } + + return registerBuild("Auto async", fields); + } +} diff --git a/tests/misc/projects/Issue11582/Main2.hx b/tests/misc/projects/Issue11582/Main2.hx new file mode 100644 index 00000000000..5ea07cdb310 --- /dev/null +++ b/tests/misc/projects/Issue11582/Main2.hx @@ -0,0 +1,25 @@ +class Main2 { + static function main() { + trace("TestFoo", TestFoo.macros); + trace("TestBar", TestBar.macros); + } +} + +class Async { + public function done() {} +} + +@:autoBuild(Macro2.buildTest()) +class BaseTest {} + +class TestFoo extends BaseTest { + @:async + function test() async.done(); +} + +@:autoBuild(Macro2.autoAsync()) +class AsyncTest extends BaseTest {} + +class TestBar extends AsyncTest { + function test() async.done(); +} diff --git a/tests/misc/projects/Issue11582/Main3.hx b/tests/misc/projects/Issue11582/Main3.hx new file mode 100644 index 00000000000..155f07acdcb --- /dev/null +++ b/tests/misc/projects/Issue11582/Main3.hx @@ -0,0 +1,25 @@ +class Main3 { + static function main() { + trace("TestFoo", TestFoo.macros); + trace("TestBar", TestBar.macros); + } +} + +class Async { + public function done() {} +} + +@:autoBuild(Macro3.buildTest()) +class BaseTest {} + +class TestFoo extends BaseTest { + @:async + function test() async.done(); +} + +@:autoBuild(Macro3.autoAsync()) +class AsyncTest extends BaseTest {} + +class TestBar extends AsyncTest { + function test() async.done(); +} diff --git a/tests/misc/projects/Issue11582/compile-early.hxml b/tests/misc/projects/Issue11582/compile-early.hxml new file mode 100644 index 00000000000..1eeff146d55 --- /dev/null +++ b/tests/misc/projects/Issue11582/compile-early.hxml @@ -0,0 +1,2 @@ +-main Main3 +--interp diff --git a/tests/misc/projects/Issue11582/compile-early.hxml.stdout b/tests/misc/projects/Issue11582/compile-early.hxml.stdout new file mode 100644 index 00000000000..bdd3facc6d3 --- /dev/null +++ b/tests/misc/projects/Issue11582/compile-early.hxml.stdout @@ -0,0 +1,2 @@ +Main3.hx:3: TestFoo,[Base Test] +Main3.hx:4: TestBar,[Auto async,Base Test] diff --git a/tests/misc/projects/Issue11582/compile-late.hxml b/tests/misc/projects/Issue11582/compile-late.hxml new file mode 100644 index 00000000000..e0934b3797b --- /dev/null +++ b/tests/misc/projects/Issue11582/compile-late.hxml @@ -0,0 +1,2 @@ +-main Main2 +--interp diff --git a/tests/misc/projects/Issue11582/compile-late.hxml.stdout b/tests/misc/projects/Issue11582/compile-late.hxml.stdout new file mode 100644 index 00000000000..23c28e6c353 --- /dev/null +++ b/tests/misc/projects/Issue11582/compile-late.hxml.stdout @@ -0,0 +1,2 @@ +Main2.hx:3: TestFoo,[Base Test] +Main2.hx:4: TestBar,[Auto async,Base Test]