Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Haxe macros to instantiate a class with parameters

Tags:

macros

haxe

I'm trying to make some dark magic with macros in Haxe, I have a class named Entity and I want to add a pool with the static and private modifiers:

Pool.hx:

package exp;

class Pool<T> {
    public function new(clazz:Class<T>) {
        
    }
}

Entity.hx:

package exp;

@:build(exp.PoolBuilder.build())
class Entity {
    public function new(){}
}

PoolBuilder.hx:

package exp;

import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;

class PoolBuilder {
    static public macro function build() : Array<Field> {
        var fields = Context.getBuildFields();
        var clazz = Context.getLocalClass();

        var typePath = { name:"Pool", pack:["exp"], params: [TPType(TPath({name: "Entity", pack: ["exp"]}))] }
        var pool = macro new $typePath(/* clazz? */);
        fields.push({
            name: "_pool",
            access: [APrivate, AStatic],
            pos: Context.currentPos(),
            kind: FVar(macro: exp.Pool, pool)
        });

        return fields;
    }
}

I have a problem with the typePath params, and passing a Class<T> as an argument to the constructor. The compiler display this error:

exp/Entity.hx:3: characters 1-7 : Invalid number of type parameters for exp.Pool

exp/Entity.hx:4: lines 4-6 : Defined in this class

Does anybody know how to solve it?

like image 618
Nazarí González Avatar asked Jan 27 '18 12:01

Nazarí González


2 Answers

Building fields manually like that is somewhat tedious - I'd recommend to use class reification instead, where you can express the field as regular Haxe code:

macro class {
    static var _pool = new Pool(/* clazz */);
}

This bypasses the "Invalid number of type parameters" issue entirely - just let type inference do the trick and omit the type parameter in new Pool().

The argument for the constructor call is of course variable, so we still need to use some expression reification. exp.Entity is a field expression, so we have to use $p{}. We can construct the type path needed for it by combining clazz.pack and clazz.name:

class PoolBuilder {
    static public macro function build():Array<Field> {
        var fields = Context.getBuildFields();
        var clazz = Context.getLocalClass().get();
        var path = clazz.pack.concat([clazz.name]); // ["exp", "Entity"]

        var extraFields = (macro class {
            static var _pool = new Pool($p{path});
        }).fields;
        return fields.concat(extraFields);
    }
}

This generates the following code (as can be seen in exp/Entity.dump with -D dump=pretty):

static var _pool:exp.Pool<exp.Entity> = new exp.Pool(exp.Entity);
like image 153
Gama11 Avatar answered Nov 08 '22 04:11

Gama11


If you prefer adding fields by fields.push({...}) instead of using class reification, you can trigger type inference by using null as a type in FVar(null, pool):

static public macro function build() : Array<Field> {
    var fields = Context.getBuildFields();
    var clazz = Context.getLocalClass().get();

    var path = clazz.pack.concat([clazz.name]); // ["exp", "Entity"]
    var pool = macro new exp.Pool($p{path});
    fields.push({
        name: "_pool",
        access: [APrivate, AStatic],
        pos: Context.currentPos(),
        kind: FVar(null, pool)
    });

    return fields;
}

This is using @gama11 trick for path. This generates the exact same code as @gama11 answer (and can be checked in the same way).

like image 5
kLabz Avatar answered Nov 08 '22 04:11

kLabz