Haxe Code Cookbook
Haxe programming cookbookMacrosAssert macro that shows sub-expression values

Assert macro that shows sub-expression values

Reading time: 2 minutes

Sometimes failed assertion checks make it difficult to tell what went wrong. For debugging the programmer not only wants to know that a check failed, but also why it failed. This macro outputs the values of all sub-expressions.

import haxe.macro.Expr;
using haxe.macro.Tools;

class Assert {
  static public macro function assert(e:Expr) {
    var s = e.toString();
    var p = e.pos;
    var el = [];
    var descs = [];
    function add(e:Expr, s:String) {
      var v = "_tmp" + el.length;
      el.push(macro var $v = $e);
      descs.push(s);
      return v;
    }
    function map(e:Expr) {
      return switch (e.expr) {
        case EConst((CInt(_) | CFloat(_) | CString(_) | CRegexp(_) | CIdent("true" | "false" | "null"))):
          e;
        case _:
          var s = e.toString();
          e = e.map(map);
          macro $i{add(e, s)};
        }
    }
    var e = map(e);
    var a = [for (i in 0...el.length) macro { expr: $v{descs[i]}, value: $i{"_tmp" + i} }];
    el.push(macro if (!$e) @:pos(p) throw new Assert.AssertionFailure($v{s}, $a{a}));
    return macro $b{el};
  }
}

private typedef AssertionPart = {
  expr: String,
  value: Dynamic
}

class AssertionFailure {
  public var message(default, null):String;
  public var parts(default, null):Array<AssertionPart>;
  public function new(message:String, parts:Array<AssertionPart>) {
    this.message = message;
    this.parts = parts;
  }

  public function toString() {
    var buf = new StringBuf();
    buf.add("Assertion failure: " + message);
    for (part in parts) {
        buf.add("\n\t" + part.expr + ": " + part.value);
    }
    return buf.toString();
  }
}

Usage

class Main {
    static function main() {
        //Error: Assertion failure: x == 7 && y == 11
        //x: 7
        //x == 7: true
        //y: 10
        //y == 11: false
        //x == 7 && y == 11: false
        var x = 7;
        var y = 10;
        Assert.assert(x == 7 && y == 11);
        
        //Error: Assertion failure: a.length > 0
        //a: []
        //a.length: 0
        //a.length > 0: false
        var a = [];
        Assert.assert(a.length > 0);
    }
}

Inspired by https://github.com/sconover/wrong


Contributors:
Gama11
Mark Knol
Simon Krajewski
Last modified:
Created:
Category:  Macros