Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macro build order #11634

Draft
wants to merge 2 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src-json/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
71 changes: 44 additions & 27 deletions src/typing/typeloadFields.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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 () ->
Expand All @@ -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 ->
Expand Down
72 changes: 72 additions & 0 deletions tests/misc/projects/Issue11582/Macro2.macro.hx
Original file line number Diff line number Diff line change
@@ -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<Field>) {
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);
}
}
72 changes: 72 additions & 0 deletions tests/misc/projects/Issue11582/Macro3.macro.hx
Original file line number Diff line number Diff line change
@@ -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<Field>) {
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);
}
}
25 changes: 25 additions & 0 deletions tests/misc/projects/Issue11582/Main2.hx
Original file line number Diff line number Diff line change
@@ -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();
}
25 changes: 25 additions & 0 deletions tests/misc/projects/Issue11582/Main3.hx
Original file line number Diff line number Diff line change
@@ -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();
}
2 changes: 2 additions & 0 deletions tests/misc/projects/Issue11582/compile-early.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-main Main3
--interp
2 changes: 2 additions & 0 deletions tests/misc/projects/Issue11582/compile-early.hxml.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Main3.hx:3: TestFoo,[Base Test]
Main3.hx:4: TestBar,[Auto async,Base Test]
2 changes: 2 additions & 0 deletions tests/misc/projects/Issue11582/compile-late.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-main Main2
--interp
2 changes: 2 additions & 0 deletions tests/misc/projects/Issue11582/compile-late.hxml.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Main2.hx:3: TestFoo,[Base Test]
Main2.hx:4: TestBar,[Auto async,Base Test]