Misiur
Misiur

Reputation: 5307

Checking if build macro already processed ancestor node

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.

Upvotes: 1

Views: 86

Answers (1)

Francis Bourre
Francis Bourre

Reputation: 268

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.

Upvotes: 1

Related Questions