简体   繁体   中英

Haxe Custom Metadata to Macro Call

Let's say that I've created a build macro that can be used like so

@:build(macros.SampleMacro.build("arg"))
class Main {}

Is it possible to convert it into a custom, shorthand metadata?

@:samplemacro("arg")
class Main {}

Any documentation on this?

I'm not sure that's possible, but you can make use of the fact that @:autoBuild() metadata works on interfaces. This is often used for "marker interfaces" like this:

class Main implements ISampleMacro {}

@:autoBuild(macros.SampleMacro.build("arg"))
interface ISampleMacro {}

However, presumably you want to have a different "arg" per usage, rather than hardcoding it. You can achieve this by using a @:const type parameter:

class Main implements ISampleMacro<"foo"> {}

@:autoBuild(macros.SampleMacro.build())
interface ISampleMacro<@:const T> {}

Extracting the type parameter's value in your build macro requires a bit more effort than simply passing a parameter to it:

switch (Context.getLocalClass().get().interfaces[0].params[0]) {
    case TInst(_.get() => t, params):
        switch (t.kind) {
            case KExpr({expr: EConst(CString(arg)), pos: _}):
                trace(arg); // "foo"
            case _:
        }
    case _:
}

After much stumbling, I've figured out that it is possible to do.

Part of the solution is to use

--macro addGlobalMetadata('$path', '@:build(Build.build())')

This lets you assign a @:build function to all of the classes in the $path. This can be used as either a compiler option or a haxeflag.

But that on its own is not enough to adopt metadata tags that take dynamic arguments. But because we now have a Build.build() that executes for all of our classes , that Build.build() function can both handle checking which classes have our custom metadata tag(s), as well as build anything for those classes we may need.

To check for our custom metadata tags, we set up our checking macro as follows:

class Build {

    static var META_STR:String = ":samplemacro";

    macro static public function build():Array<Field> {

        // We check only those Contexts for where a class type exists
        var localClass:Null<Ref<ClassType>> = Context.getLocalClass();
        if(localClass == null) return null; // no class type

        // We check if the metadata for the class contains our 
        // custom metadata string at all
        if(!localClass.get().meta.has(META_STR)) return null;

        // This class does have our custom metadata!
        // Because there may be more than one of the same type
        // of metadata, we extract a list of all the custom metadata tags

        var customTags = localClass.get().meta.extract(META_STR);

        // For each tag we can get at the arguments passed in 
        // by accessing the params field
        for(customTag in customTags){
            var params = customTag.params;

            // Here we can handle each of our @:samplemacro(params) tags,
            // save the params for use later, or 
            // pass the arguments over to another class to deal with
        }

        var fields = Context.getBuildFields();

        // Modify the class fields the way you want

        // Optionally destroy the metadata tags afterwards
        // with localClass.get().meta.remove(META_STR);

        return fields;
    }
}

More details on addGlobalMetadata can be found at: https://stackoverflow.com/a/38075492/1502818

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM