Skip to content

Instantly share code, notes, and snippets.

@player-03
Created August 10, 2026 03:28
Show Gist options
  • Select an option

  • Save player-03/1f6d2d2c8811d70ab470d070defa8046 to your computer and use it in GitHub Desktop.

Select an option

Save player-03/1f6d2d2c8811d70ab470d070defa8046 to your computer and use it in GitHub Desktop.
UTest error message generator
import haxe.macro.Expr;
import haxe.macro.Printer;
import haxe.PosInfos;
#if !macro
import utest.Assert as UAssert;
import utest.Assertation;
#end
using haxe.macro.Context;
using haxe.macro.ComplexTypeTools;
/**
* A drop-in replacement for `utest.Assert` that generates more detailed error
* messages for `isTrue()` and `isFalse()`.
*/
class Assert {
/**
* @param message If null, defaults to the source code for `cond` followed
* by the runtime values. For instance, `Assert.isTrue(x == y)` would print
* something like "Failure: x == y. Values: 5 == 4".
*/
public static macro function isTrue(condition:ExprOf<Bool>, ?message:ExprOf<String>, ?pos:ExprOf<PosInfos>):Expr {
return switch(message) {
case null, macro null:
addMessage("isTrue", condition, pos);
default:
if(pos.expr.match(EConst(CIdent("null")))
&& message.typeof().unify((macro:haxe.PosInfos).toType())) {
return addMessage("isTrue", condition, message);
}
buildAssertion("isTrue", condition, message, pos);
};
}
/**
* @param message If null, defaults to the source code for `cond` followed
* by the runtime values. For instance, `Assert.isFalse(x == y)` would print
* something like "Failure: x == y should be false. Values: 4 == 4".
*/
public static macro function isFalse(condition:ExprOf<Bool>, ?message:ExprOf<String>, ?pos:ExprOf<PosInfos>):Expr {
return switch(message) {
case null, macro null:
addMessage("isFalse", condition, pos);
default:
if(pos.expr.match(EConst(CIdent("null")))
&& message.typeof().unify((macro:haxe.PosInfos).toType())) {
return addMessage("isTrue", condition, message);
}
buildAssertion("isFalse", condition, message, pos);
};
}
#if !macro
public var results(get, set):List<Assertation>;
private inline function get_results():List<Assertation> {
return UAssert.results;
}
private inline function set_results(value:List<Assertation>):List<Assertation> {
return UAssert.results = value;
}
public static inline function isNull<T>(value:Null<T>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.isNull(value, msg, pos);
}
public static inline function notNull(value:Null<Any>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.notNull(value, msg, pos);
}
public static inline function isOfType(value:Null<Any>, type:Any, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.isOfType(value, type, msg, pos);
}
public static inline function notEquals<T>(expected:Null<T>, value:Null<T>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.notEquals(expected, value, msg, pos);
}
public static inline function equals<T>(expected:Null<T>, value:Null<T>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.equals(expected, value, msg, pos);
}
public static inline function match(pattern:EReg, value:String, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.match(pattern, value, msg, pos);
}
public static inline function floatEquals(expected:Float, value:Float, ?approx:Float, ?msg:String, ?pos:PosInfos):Bool{
return UAssert.floatEquals(expected, value, approx, msg, pos);
}
public static inline function same(expected:Null<Any>, value:Null<Any>, ?recursive:Bool, ?msg:String, ?approx:Float, ?pos:PosInfos):Bool {
return UAssert.same(expected, value, recursive, msg, approx, pos);
}
public static inline function similar(expected:Null<Any>, value:Null<Any>, recursive:Bool = true, ?msg:String, approx:Float = 1e-5, ?pos:PosInfos):Bool {
return UAssert.similar(expected, value, recursive, msg, approx, pos);
}
public static inline function exception<T>(method:() -> Void, ?type:Class<T>, ?condition:(e:T)->Bool, ?msgNotThrown:String, ?msgWrongType:String, ?msgWrongCondition:String, ?pos:PosInfos):Bool {
return UAssert.exception(method, type, condition, msgNotThrown, msgWrongType, msgWrongCondition, pos);
}
public static inline function allows<T>(possibilities:Array<T>, value:T, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.allows(possibilities, value, msg, pos);
}
public static inline function contains<T>(match:T, values:Array<T>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.contains(match, values, msg, pos);
}
public static inline function notContains<T>(match:T, values:Array<T>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.notContains(match, values, msg, pos);
}
public static inline function stringContains(match:String, value:Null<String>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.stringContains(match, value, msg, pos);
}
public static inline function stringSequence(sequence:Array<String>, value:Null<String>, ?msg:String, ?pos:PosInfos):Bool {
return UAssert.stringSequence(sequence, value, pos);
}
public static inline function pass(msg = "pass expected", ?pos:PosInfos):Bool {
return UAssert.pass(msg, pos);
}
public static inline function fail(msg = "failure expected", ?pos:PosInfos):Bool {
return UAssert.fail(msg, pos);
}
public static inline function warn(msg:String):Void {
UAssert.warn(msg);
}
#else
private static final printer:Printer = new Printer();
private static function addMessage(assertion:String, condition:Expr, pos:Expr):Expr {
return switch(condition.expr) {
case EBinop(op = OpEq | OpNotEq | OpGt | OpGte | OpLt | OpLte, left, right):
buildBinopAssertion(assertion, condition, op, left, right, pos);
case ECall(func, args):
buildCallAssertion(assertion, condition, func, args, pos);
case EUnop(OpNot, false, condition):
addMessage(assertion == "isTrue" ? "isFalse" : "isTrue", condition, pos);
case EParenthesis(condition):
addMessage(assertion, condition, pos);
default:
buildAssertion(assertion, condition, macro null, pos);
};
}
private static function buildAssertion(assertion:String, condition:Expr, message:Expr, pos:Expr):Expr {
return switch(pos) {
case null, macro null:
macro @:pos(Context.currentPos()) utest.Assert.$assertion($condition, $message);
default:
macro @:pos(Context.currentPos()) utest.Assert.$assertion($condition, $message, $pos);
};
}
private static function buildCallAssertion(assertion:String, condition:Expr, func:Expr, args:Array<Expr>, pos:Expr):Expr {
final vars:Array<Var> = [];
final funcValue:Expr = switch(func.expr) {
case EField(thisValue, funcName) if(!isCapitalized(thisValue)):
vars.push({ name: "_utest_this", expr: thisValue });
func = { expr: EField(macro _utest_this, funcName), pos: func.pos };
//Display the value of `_utest_this` and the name of `func`. So
//if `condition` is `[a, b].contains(c)`, `funcValue` would be
//`Std.string([a, b]) + ".contains"`.
macro Std.string(_utest_this) + $v{ "." + funcName };
default:
macro $v{ printer.printExpr(func) };
};
final argValues:Array<Expr> = [];
final args:Array<Expr> = [for(i => arg in args) {
if(isCapitalized(arg)) {
argValues.push(macro $v{ printer.printExpr(arg) });
arg;
} else {
final varName = "_utest_arg_" + i;
vars.push({ name: varName, expr: arg });
argValues.push(macro Std.string($i{ varName }));
macro $i{ varName };
}
}];
final variableDeclarations:Expr = { expr: EVars(vars), pos: condition.pos };
final shouldBeFalse:String = assertion == "isFalse" ? " should be false" : "";
return macro @:pos(condition.pos) {
$variableDeclarations;
final _utest_msg = "Failed: " + $v{ printer.printExpr(condition) + shouldBeFalse } + ". "
+ "Values: " + $funcValue + "(" + $a{ argValues }.join(", ") + ")";
${ buildAssertion(assertion, macro @:pos(func.pos) $func($a{ args }), macro _utest_msg, pos) };
};
}
private static function isCapitalized(expr:Expr) {
return switch(expr.expr) {
case EConst(CIdent(ident)), EField(_, ident):
~/^[A-Z]/.match(ident);
default:
false;
}
}
private static function buildBinopAssertion(assertion:String, condition:Expr, op:Binop, left:Expr, right:Expr, pos:Expr):Expr {
final conditionString:String = printer.printExpr(condition)
+ (assertion == "isFalse" ? " should be false" : "");
final condition:Expr = {
expr: EBinop(op, macro @:pos(left.pos) _utest_left, macro @:pos(right.pos) _utest_right),
pos: condition.pos
};
return macro @:pos(condition.pos) {
final _utest_left = $left;
final _utest_right = $right;
final _utest_msg:String = "Failed: " + $v{ conditionString } + ". "
+ "Values: " + _utest_left + " " + $v{ printer.printBinop(op) } + " " + _utest_right;
${ buildAssertion(assertion, condition, macro @:pos(condition.pos) _utest_msg, pos) };
};
}
#end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment