Skip to content

Instantly share code, notes, and snippets.

@nadako
Created June 9, 2014 19:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nadako/b70da59d3c68a66ef5ee to your computer and use it in GitHub Desktop.
Save nadako/b70da59d3c68a66ef5ee to your computer and use it in GitHub Desktop.
Runtime type check generator.
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
using haxe.macro.Tools;
#end
@:enum abstract MyEnum<T>(T) {}
typedef A = MyEnum<Float>;
typedef B = Array<{a:Int, ?b:Array<{?a:String}>}>;
typedef C = Dynamic<Int>;
class Main
{
static function main()
{
var a:Dynamic = 1;
check(a, Int);
check(a, Float);
check(a, Bool);
check(a, A);
check(a, String);
check(a, B);
check(a, C);
// check(a, Dynamic);
}
static macro function check(eo, et)
{
var type = Context.getType(et.toString());
return generateCheck(eo, type, et.pos, macro "args");
}
#if macro
static function generateCheck(e:Expr, type:haxe.macro.Type, pos:Position, errorPrefix:Expr):Expr
{
switch (type.follow())
{
case TAbstract(_.get() => ab, params):
switch [ab, params]
{
case [{pack: [], name: "Int"}, []]:
return macro if (!Std.is($e, Int)) throw $errorPrefix + ': not an integer';
case [{pack: [], name: "Float"}, []]:
return macro if (!Std.is($e, Float)) throw $errorPrefix + ': not a float';
case [{pack: [], name: "Bool"}, []]:
return macro if (!Std.is($e, Bool)) throw $errorPrefix + ': not a boolean';
default:
var type = ab.type.applyTypeParameters(ab.params, params);
return generateCheck(e, type, pos, errorPrefix);
}
case TInst(_.get() => cl, params):
switch [cl, params]
{
case [{pack: [], name: "String"}, []]:
return macro if (!Std.is($e, String)) throw $errorPrefix + ': not a string';
case [{pack: [], name: "Array"}, [elemType]]:
var ct = type.toComplexType();
return macro {
if (!Std.is($e, Array)) throw $errorPrefix + ': not an array';
var i = 0;
for (v in ($e : $ct))
{
${generateCheck(macro v, elemType, pos, macro $errorPrefix + "[" + i + "]")};
}
}
default:
}
case TAnonymous(_.get().fields => fields):
var checks = [];
for (field in fields)
{
var fieldName = field.name;
var optional = field.meta.has(":optional");
var check = generateCheck(macro $e.$fieldName, field.type, field.pos, macro $errorPrefix + "." + $v{fieldName});
var hasField = macro Reflect.hasField($e, $v{fieldName});
if (!optional)
{
checks.push(macro if (!$hasField) throw $v{"missing field " + fieldName});
checks.push(check);
}
else
{
switch (check.expr)
{
case EIf(econd, error, null):
checks.push(macro if ($hasField && $econd) $error);
default:
checks.push(macro if ($hasField) $check);
}
}
}
return macro $b{checks};
case TDynamic(null):
return macro {};
case TDynamic(t):
return macro {
var tmp = $e;
for (field in Reflect.fields(tmp))
{
var value = Reflect.field(tmp, field);
${generateCheck(macro value, t, pos, macro $errorPrefix + "." + field)}
}
}
default:
}
throw new Error("Unsupported type " + type.toString(), pos);
}
#end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment