Assume you have type-building macro, interface invoking @:autoBuild
using aforementioned macro, class implementing the interface and class extending it. Macro will fail if the class doesn't contain specific method.
Like so:
Macro.hx
package;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
class Macro
{
macro public function build():Array<Field>
{
var fields = Context.getBuildFields();
for (field in fields) {
if (field.name == "hello") {
//Do some modifications
return fields;
}
}
Context.error('${Context.getLocalClass().toString()} doesn\'t contain a method `hello`', Context.currentPos());
return null;
}
}
I.hx
package;
@:autoBuild(Macro.build())
interface I {}
Foobar.hx
package;
class Foobar implements I
{
public function new() {}
public function hello(person:String)
{
return 'Hello $person!';
}
}
Foo.hx
package;
@:keep
class Foo extends Foobar {}
As you can see, we're checking if field "hello" exists. However, Context.getBuildFields
contains only fields of current class, and build will fail for Foo
.
This is where my idea comes in: Why not just check if any ancestor was already processed? We'll change Macro.hx to reflect just that:
Macro.hx
package;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
class Macro
{
macro public function build():Array<Field>
{
var c = Context.getLocalClass().get();
if(isAncestorAlreadyProcessed(c)) {
return null;
}
var fields = Context.getBuildFields();
for (field in fields) {
if (field.name == "hello") {
//Do some modifications
c.meta.add(":processed", [], c.pos);
return fields;
}
}
Context.error('${Context.getLocalClass().toString()} doesn\'t contain a method `hello`', Context.currentPos());
return null;
}
private static function isAncestorAlreadyProcessed(c:ClassType)
{
if (c.meta.has(":processed")) return true;
if (c.superClass == null) return false;
return isAncestorAlreadyProcessed(c.superClass.t.get());
}
}
And for the main questions: Do I misunderstand haxe macro type building? Is there a more viable way of making this work? Does my code fail in specific scenarios? Are there any harmful side-effects caused by this code?
I'm trying to resolve this issue.
No, this is the way to go, use metadata to store information of the classes you processed (source).
Another way, if you don't need this information at runtime, is to use a static array on a dedicated class like here. Afterwards, you can even push this information in your compiled code, see here.
Hope that helps.