I want to do compile-time assertions in Haxe. It would be nice to do something like:
static inline var important_number = 42;
public function f():Void {
static_assert(important_number > 64, "important number is too small for this implementation!");
}
My question is: are Haxe macros the right route here, otherwise what is the best way to do compile-time assertions in Haxe?
Below I have a macro which works for this if you just pass it true/false (though I suppose it should be returning nothing or a noop). But I am unsure how to make this work for the more general case of "anything that eventually becomes a boolean at compile-time".
class Assert {
/* Static assert */
macro static public function s(e:Expr, errorString:String):Expr {
switch(e.expr) {
case EConst(c):
switch(c) {
case CIdent("true"):
return e;
case CIdent("false"):
throw new Error(errorString, e.pos);
default:
throw new Error("I only accept true/false right now", e.pos);
}
default:
throw new Error("I only accept true/false right now", e.pos);
}
}
}
Assert.s(false, "yep, it's a compile time error");
Assert.s(true, "business as usual");
Assert.s(6 == 9, "i don't seem to work yet");
Update 1:
There is #error which can be used for some simple cases like:
#if ios
trace("it just works!");
#else
#error("you didn't implement this yet!");
#end
Solution:
So here is what I'm using now, there are probably caveats but it seems to work for simple static assertions:
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.ExprTools;
class Assert {
/* Static assert */
macro static public function s(expr:Expr, ?error:String):Expr {
if (error == null) {
error = "";
}
if (expr == null) {
throw new Error("Expression must be non-null", expr.pos);
}
var value = ExprTools.getValue(Context.getTypedExpr(Context.typeExpr(expr)));
if (value == null) {
throw new Error("Expression value is null", expr.pos);
}
else if (value != true && value != false) {
throw new Error("Expression does not evaluate to a boolean value", expr.pos);
}
else if(value == false) {
throw new Error("Assertion failure: " + ExprTools.toString(expr) + " " + "[ " + error + " ]", expr.pos);
}
return macro { };
}
}
To evaluate an Expr
and get the value of it at compile-time, we can use ExprTools.getValue. Looking at its source, it is in fact using a technique similar to the one posted in the question.
Making it even more robust, we can do ExprTools.getValue(Context.getTypedExpr(Context.typeExpr(expr)))
, such that all inline variables or even macro functions inside expr
will be resolved.
For returning a no-op, we can simply return macro {};
.