Initialize class instance from Expr in macro

I was wondering whether it is possible to initialize a class instance from an expression in a Macro so you can then call functions on it. One of the cases is to pass a class reference to a type building macro and instantiate and use it there.

1 Like

You can.

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

class Test {
	public static function main() {
		make(Test).hello();
	}

	public function new() {
	}

	public function hello() {
		trace("hello");
	}

	macro static function make (e:Expr) : Expr {
		return switch (e.expr) {
			case EConst(CIdent(cls)):
				switch (Context.getType(cls)) {
					case TInst (_.get() => t, _):
						var path = { name: t.module, sub: t.module == t.name ? null : t.name, pack: t.pack };
						macro new $path();
					default:
						throw "Invalid type";
				}
			default:
				throw "Invalid argument";
		}
	}
}

I’m sure this has a lot of edge cases, but works.

Hi Valentin,

Really appreciate the reply! In this example the macro returns an AST that creates an instance of Test. This should be the same as just writing new Test();

What I am trying to do (I probably did not explain well enough) is to create an instance of Test in the macro. So to keep your example:

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

class Test {
	public static function main() {
		make(Test);
	}

	public function new() {
	}

	public function hello() {
		trace("hello");
	}

	macro static function make (e:Expr) : Expr {
		return switch (e.expr) {
			case EConst(CIdent(cls)):
				switch (Context.getType(cls)) {
					case TInst (_.get() => t, _):
						var path = { name: t.module, sub: t.module == t.name ? null : t.name, pack: t.pack };
						// Create a new instance from the path/type here.
						var instance = ?
						instance.hello();
					default:
						throw "Invalid type";
				}
			default:
				throw "Invalid argument";
		}
	}
}

Again thanks for taking the time to reply!

Oh I see. You can call the make macro from another macro if you want.

static macro function atCompileTime() {
	var h = make(Test);
	h.hello();
	return macro null;
}

Although the returned type is dynamic, so if you misspell the hello function you’ll have a compile time exception:

Test.hx:8: characters 3-18 : Uncaught exception Cannot call null
No stack information available, consider compiling with -D eval-stack

Another solution is to do it without a macro.

import haxe.Constraints.Constructible;

class Test {
	public static function main() {
		construct(Test);
	}

	public function new() {
	}

	public function hello() {
		trace("hello");
	}

	@:generic
	public static function construct<T:(Constructible<Void->Void>,{ function hello():Void; })> (cls:Class<T>)
	{
		var h = new T();
		h.hello();
	}
}

You can call construct in a macro if you want.

This time it is correctly typed and you’ll get a compilation error if you misspell hello,
but you need to add the function you want to call to the type constraint of the construct function, otherwise haxe won’t know that what you pass is of the correct type and can call this function.

All non-macro classes are also available in macro. So you can just use Type.createInstance() in macro. The only thing you should consider is possible conflict between Type and haxe.macro.Type. It can be avoided by importing Type with alias.

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

class Test {
	public static function main() {
		make(Test);
	}

	public function new() {
	}

	public function hello() {
		trace("hello");
	}

	macro static function make (e:Expr) : Expr {
    
		var typeName: String = haxe.macro.ExprTools.toString(e);
		var cls: Class<Dynamic> = RunTimeType.resolveClass(typeName);
		var inst: Dynamic = RunTimeType.createInstance(cls, []);
		inst.hello();
		return macro null;
	}
}

Thanks guys I will definitely look into this! I will let you know whether I got things working or not.