Skip to content

Instantly share code, notes, and snippets.

@porfirioribeiro
Created June 10, 2013 19:12
Show Gist options
  • Save porfirioribeiro/5751375 to your computer and use it in GitHub Desktop.
Save porfirioribeiro/5751375 to your computer and use it in GitHub Desktop.
Haxe TypeCheck in JavaScript with macros

This is a simple replacement to Std.is to use macros to generate checks specific to what we are checking against.

Main.hx - Simple test hx file Main.js - Generated JavaScript code TypeCheck.hx - Definition of TypeCheck Test.hs - Unit tests

package ;
using TypeCheck;
/**
* @author Porfirio
*/
class Main {
public function new() {}
static function main() {
var m = new Main();
trace(TypeCheck.is(m, Main));
//using TypeCheck; does the trick also!
trace(m.is(Main));
trace("".is(String));
}
}
(function () { "use strict";
var Main = function() {
};
Main.main = function() {
var m = new Main();
console.log((m instanceof Main));
console.log((m instanceof Main));
console.log(typeof("") == "string");
}
Main.main();
})();
package ;
import haxe.unit.TestRunner;
import haxe.unit.TestCase;
/**
* @author Porfirio
*/
class Test extends TestRunner
{
public function new() {
super();
add(new TestNull());
add(new TestNatives());
add(new TestDynamicClass());
}
static function main() {
new Test().run();
}
}
enum ETest {
A;
B;
C(a:Int);
}
interface IBase {
}
class Base implements IBase {
public static var b:Base;
public function new() { }
public static function __init__() {
b = new Base();
}
}
class Sub extends Base {
public function new() { super(); }
}
class TestNull extends TestCase {
var null1 = null;
public function testNatives() {
assertFalse(TypeCheck.is(null, Int));
assertFalse(TypeCheck.is(null, Float));
assertFalse(TypeCheck.is(null, Bool));
assertFalse(TypeCheck.is(null, String));
assertFalse(TypeCheck.is(null, Class));
assertFalse(TypeCheck.is(null, Enum));
assertTrue(TypeCheck.is(null, Dynamic));//This check is always true
assertFalse(TypeCheck.is(null1, Int));
assertFalse(TypeCheck.is(null1, Float));
assertFalse(TypeCheck.is(null1, Bool));
assertFalse(TypeCheck.is(null1, String));
assertFalse(TypeCheck.is(null1, Class));
assertFalse(TypeCheck.is(null1, Enum));
assertTrue(TypeCheck.is(null1, Dynamic));//This check is always true
}
public function testInterface() {
assertFalse(TypeCheck.is(null, IBase));
assertFalse(TypeCheck.is(null1, IBase));
}
public function testClass() {
assertFalse(TypeCheck.is(null, Base));
assertFalse(TypeCheck.is(null1, Base));
}
public function testUnum() {
assertFalse(TypeCheck.is(null, ETest));
assertFalse(TypeCheck.is(null1, ETest));
}
}
class TestNatives extends TestCase {
public function testNumbers() {
assertTrue(TypeCheck.is(1, Int));
assertTrue(TypeCheck.is(1, Float));
//assertTrue(TypeCheck.is(1.1, Int));//Compile time error: Float should be Int
assertTrue(TypeCheck.is(1.1, Float));
var i = 1, f = 1.1;
assertTrue(TypeCheck.is(i, Int));
assertTrue(TypeCheck.is(i, Float));
//assertTrue(TypeCheck.is(f, Int));//Compile time error: Float should be Int
assertTrue(TypeCheck.is(f, Float));
var f2:Dynamic;
f2 = 1;
assertTrue(TypeCheck.is(f2, Int));
assertTrue(TypeCheck.is(f2, Float));
f2 = 1.1;
assertFalse(TypeCheck.is(f2, Int));
assertTrue(TypeCheck.is(f2, Float));
}
public function testBool() {
assertTrue(TypeCheck.is(true, Bool));
assertTrue(TypeCheck.is(false, Bool));
assertFalse(TypeCheck.is(1, Bool));
var b = false;
assertTrue(TypeCheck.is(b, Bool));
}
public function testString() {
assertTrue(TypeCheck.is("str", String));
var str = "";
assertTrue(TypeCheck.is(str, String));
assertFalse(TypeCheck.is(null, String));
assertFalse(TypeCheck.is(1, String));
assertFalse(TypeCheck.is(true, String));
}
public function testClass() {
assertTrue(TypeCheck.is(Base, Class));
assertTrue(TypeCheck.is(IBase, Class));
assertTrue(TypeCheck.is(Int, Class));
var c = String;
assertTrue(TypeCheck.is(c, Class));
}
public function testEnum() {
assertTrue(TypeCheck.is(ETest, Enum));
var e = ETest;
assertTrue(TypeCheck.is(e, Enum));
}
public function testDynamic() {
assertTrue(TypeCheck.is(null, Dynamic));//Testing against dynamic always give true no matter tyhe value!
}
}
class TestDynamicClass extends TestCase {
//In this test what matters is the code generated more than the result...
public function testClass() {
var c:Class<Dynamic>;
c = String;
assertTrue(TypeCheck.is("", c));
var c:Class<IBase>=null;
var s:Dynamic = null;
assertFalse(TypeCheck.is(s, c));
s = new Sub();
c = IBase;
assertTrue(TypeCheck.is(s, c));
c = Base;
assertTrue(TypeCheck.is(s, c));
c = Sub;
assertTrue(TypeCheck.is(s, c));
}
public function testEnum() {
var e:Enum<Dynamic>=null;
var v:ETest=null;
assertFalse(TypeCheck.is(v, e));
e = ETest;
v = ETest.A;
assertTrue(TypeCheck.is(v, e));
}
}
package ;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
#end
using StringTools;
/**
* @author Porfirio
*/
class TypeCheck {
macro public static function is(o:Expr, c:ExprOf<Class<Dynamic>>):ExprOf<Bool> {
if (!Context.defined("js"))
return macro Std.is($o, $c); //This code is for JS only
var pos = Context.currentPos();
var oType = Context.typeof(o);
var t=switch (Context.typeof(c)) {
case TType(t, params): t.get();
case TAbstract(t, params):
switch (t.get().name) {
//Handle a typed c parameter with Enum
case "Enum": return macro (TypeCheck.is($o, Array) && untyped $o.__enum__ == $c);
//Handle a typed c parameter with Class<T> where T is not a js native
case "Class":
switch (params[0]) {
case TInst(t, _):
switch (t.get().name) {
case "Int", "Float", "Bool", "String", "Class", "Enum", "Dynamic":
case _: return macro untyped $o!=null && (__instanceof__($o, $c) || js.Boot.__interfLoop(js.Boot.getClass($o), $c));
}
case _:
}
} null;
case _: null;
}
//if the c is dynamic we cant figure out what check to do at compile time, fallback to old runtime check
if (t == null)
return macro js.Boot.__instanceof($o, $c);
var cName = ((t.pack.length > 0)?t.pack.join(".") + ".":"") + t.name.replace("#", "");
var cType = Context.getType(cName);
switch(cName) {
case "Int":
return macro (($o|0) == $o);
case "Float":
return macro untyped (__js__("typeof")($o) == "number");
case "Bool":
return macro untyped (__js__("typeof")($o) == "boolean");
case "String":
return macro untyped (__js__("typeof")($o) == "string");
case "Class":
return macro untyped ($o != null && __define_feature__("js.Boot.isClass", $o.__name__ != null));
case "Enum":
return macro untyped ($o != null && __define_feature__("js.Boot.isEnum", $o.__ename__ != null));
case "Dynamic":
return macro true;
case _:
switch (cType) {
case TInst(name, _):
if (name.get().isInterface) {
return macro untyped ($o!=null && js.Boot.__interfLoop(js.Boot.getClass($o), $c));
}else {
return macro untyped __instanceof__($o, $c);
}
case TEnum(e, _):
return macro (TypeCheck.is($o, Array) && untyped $o.__enum__ == $c);
case _:
}
}
//At the end of the day noone could solve our problem, just fallback to runtime check
return macro js.Boot.__instanceof($o, $c);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment