package hscript; import hscript.Expr; /** This is a special type that can be used in API. It will be type-checked as `Script` but will compile/execute as `Real` **/ typedef TypeCheck = Real; enum TType { TMono( r : { r : TType } ); TVoid; TInt; TFloat; TBool; TDynamic; TParam( name : String ); TUnresolved( name : String ); TNull( t : TType ); TInst( c : CClass, args : Array ); TEnum( e : CEnum, args : Array ); TType( t : CTypedef, args : Array ); TAbstract( a : CAbstract, args : Array ); TFun( args : Array<{ name : String, opt : Bool, t : TType }>, ret : TType ); TAnon( fields : Array<{ name : String, opt : Bool, t : TType }> ); TLazy( f : Void -> TType ); } private enum WithType { NoValue; Value; WithType( t : TType ); } enum CTypedecl { CTClass( c : CClass ); CTEnum( e : CEnum ); CTTypedef( t : CTypedef ); CTAlias( t : TType ); CTAbstract( a : CAbstract ); } typedef CNamedType = { var name : String; var params : Array; } typedef CClass = {> CNamedType, @:optional var superClass : TType; @:optional var constructor : CField; @:optional var interfaces : Array; @:optional var isInterface : Bool; var fields : Map; var statics : Map; } typedef CField = { var isPublic : Bool; var canWrite : Bool; var complete : Bool; var params : Array; var name : String; var t : TType; } typedef CEnum = {> CNamedType, var constructors : Array<{ name : String, ?args : Array<{ name : String, opt : Bool, t : TType }> }>; } typedef CTypedef = {> CNamedType, var t : TType; } typedef CAbstract = {> CNamedType, var t : TType; } class Completion { public var expr : Expr; public var t : TType; public function new(expr,t) { this.expr = expr; this.t = t; } } @:allow(hscript.Checker) class CheckerTypes { var types : Map = new Map(); var t_string : TType; var localParams : Map; public function new() { types = new Map(); types.set("Void",CTAlias(TVoid)); types.set("Int",CTAlias(TInt)); types.set("Float",CTAlias(TFloat)); types.set("Bool",CTAlias(TBool)); types.set("Dynamic",CTAlias(TDynamic)); } public function addXmlApi( api : Xml ) { var types = new haxe.rtti.XmlParser(); types.process(api, ""); var todo = []; for( v in types.root ) addXmlType(v,todo); for( f in todo ) f(); t_string = getType("String"); } function addXmlType(x:haxe.rtti.CType.TypeTree,todo:ArrayVoid>) { switch (x) { case TPackage(name, full, subs): for( s in subs ) addXmlType(s,todo); case TClassdecl(c): if( types.exists(c.path) ) return; var cl : CClass = { name : c.path, params : [], fields : new Map(), statics : new Map(), }; if( c.isInterface ) cl.isInterface = true; for( p in c.params ) cl.params.push(TParam(p)); todo.push(function() { localParams = [for( t in cl.params ) c.path+"."+Checker.typeStr(t) => t]; if( c.superClass != null ) cl.superClass = getType(c.superClass.path, [for( t in c.superClass.params ) makeXmlType(t)]); if( c.interfaces != null ) { cl.interfaces = []; for( i in c.interfaces ) cl.interfaces.push(getType(i.path, [for( t in i.params ) makeXmlType(t)])); } var pkeys = []; for( f in c.fields ) { if( f.isOverride || f.name.substr(0,4) == "get_" || f.name.substr(0,4) == "set_" ) continue; var skip = false; var complete = !StringTools.startsWith(f.name,"__"); // __uid, etc. (no metadata in such fields) for( m in f.meta ) { if( m.name == ":noScript" ) { skip = true; break; } if( m.name == ":noCompletion" ) complete = false; } if( skip ) continue; var fl : CField = { isPublic : f.isPublic, canWrite : f.set.match(RNormal | RCall(_) | RDynamic), complete : complete, params : [], name : f.name, t : null }; for( p in f.params ) { var pt = TParam(p); var key = f.name+"."+p; pkeys.push(key); fl.params.push(pt); localParams.set(key, pt); } fl.t = makeXmlType(f.type); while( pkeys.length > 0 ) localParams.remove(pkeys.pop()); if( fl.name == "new" ) cl.constructor = fl; else cl.fields.set(f.name, fl); } localParams = null; }); types.set(cl.name, CTClass(cl)); case TEnumdecl(e): if( types.exists(e.path) ) return; var en : CEnum = { name : e.path, params : [], constructors: [], }; for( p in e.params ) en.params.push(TParam(p)); todo.push(function() { localParams = [for( t in en.params ) e.path+"."+Checker.typeStr(t) => t]; for( c in e.constructors ) en.constructors.push({ name : c.name, args : c.args == null ? null : [for( a in c.args ) { name : a.name, opt : a.opt, t : makeXmlType(a.t) }] }); localParams = null; }); types.set(en.name, CTEnum(en)); case TTypedecl(t): if( types.exists(t.path) ) return; var td : CTypedef = { name : t.path, params : [], t : null, }; for( p in t.params ) td.params.push(TParam(p)); if( t.path == "hscript.TypeCheck" ) td.params.reverse(); todo.push(function() { localParams = [for( pt in td.params ) t.path+"."+Checker.typeStr(pt) => pt]; td.t = makeXmlType(t.type); localParams = null; }); types.set(t.path, CTTypedef(td)); case TAbstractdecl(a): if( types.exists(a.path) ) return; var ta : CAbstract = { name : a.path, params : [], t : null, }; for( p in a.params ) ta.params.push(TParam(p)); todo.push(function() { localParams = [for( t in ta.params ) a.path+"."+Checker.typeStr(t) => t]; ta.t = makeXmlType(a.athis); localParams = null; }); types.set(a.path, CTAbstract(ta)); } } function makeXmlType( t : haxe.rtti.CType.CType ) : TType { return switch (t) { case CUnknown: TUnresolved("Unknown"); case CEnum(name, params): getType(name,[for( t in params ) makeXmlType(t)]); case CClass(name, params): getType(name,[for( t in params ) makeXmlType(t)]); case CTypedef(name, params): getType(name,[for( t in params ) makeXmlType(t)]); case CFunction(args, ret): TFun([for( a in args ) { name : a.name, opt : a.opt, t : makeXmlType(a.t) }], makeXmlType(ret)); case CAnonymous(fields): inline function isOpt(m:haxe.rtti.CType.MetaData) { if( m == null ) return false; var b = false; for( m in m ) if( m.name == ":optional" ) { b = true; break; } return b; } TAnon([for( f in fields ) { name : f.name, t : makeXmlType(f.type), opt : isOpt(f.meta) }]); case CDynamic(t): TDynamic; case CAbstract(name, params): switch( name ) { default: getType(name,[for( t in params ) makeXmlType(t)]); } } } function getType( name : String, ?args : Array ) : TType { if( localParams != null ) { var t = localParams.get(name); if( t != null ) return t; } var t = resolve(name,args); if( t == null ) { var pack = name.split("."); if( pack.length > 1 ) { // bugfix for some args reported as pack._Name.Name while they are not private var priv = pack[pack.length-2]; if( priv.charCodeAt(0) == "_".code ) { pack.remove(priv); return getType(pack.join("."), args); } } return TUnresolved(name); // most likely private class } return t; } public function resolve( name : String, ?args : Array ) : TType { if( name == "Null" ) { if( args == null || args.length != 1 ) throw "Missing Null parameter"; return TNull(args[0]); } var t = types.get(name); if( t == null ) return null; if( args == null ) args = []; return switch( t ) { case CTClass(c): TInst(c,args); case CTEnum(e): TEnum(e,args); case CTTypedef(t): TType(t,args); case CTAbstract(a): TAbstract(a, args); case CTAlias(t): t; } } } class Checker { public var types : CheckerTypes; var locals : Map; var globals : Map = new Map(); var events : Map = new Map(); var currentFunType : TType; var isCompletion : Bool; var allowDefine : Bool; public var allowAsync : Bool; public var allowReturn : Null; public var allowGlobalsDefine : Bool; public var allowUntypedMeta : Bool; public function new( ?types ) { if( types == null ) types = new CheckerTypes(); this.types = types; } public function setGlobals( cl : CClass ) { while( true ) { for( f in cl.fields ) if( f.isPublic ) setGlobal(f.name, f.params.length == 0 ? f.t : TLazy(function() return apply(f.t,f.params,[for( i in 0...f.params.length) makeMono()]))); if( cl.superClass == null ) break; cl = switch( cl.superClass ) { case TInst(c,_): c; default: throw "assert"; } } } public function removeGlobal( name : String ) { globals.remove(name); } public function setGlobal( name : String, type : TType ) { globals.set(name, type); } public function setEvent( name : String, type : TType ) { events.set(name, type); } public function getGlobals() { return globals; } function typeArgs( args : Array, pos : Expr ) { return [for( i in 0...args.length ) { var a = args[i]; var at = a.t == null ? makeMono() : makeType(a.t, pos); { name : a.name, opt : a.opt, t : at }; }]; } public function check( expr : Expr, ?withType : WithType, ?isCompletion = false ) { if( withType == null ) withType = NoValue; locals = new Map(); allowDefine = allowGlobalsDefine; this.isCompletion = isCompletion; switch( edef(expr) ) { case EBlock(el): var delayed = []; var last = TVoid; for( e in el ) { while( true ) { switch( edef(e) ) { case EMeta(_,_,e2): e = e2; default: break; } } switch( edef(e) ) { case EFunction(args,_,name,ret) if( name != null ): var tret = ret == null ? makeMono() : makeType(ret, e); var ft = TFun(typeArgs(args,e),tret); locals.set(name, ft); delayed.push(function() { currentFunType = ft; typeExpr(e, NoValue); return ft; }); default: for( f in delayed ) f(); delayed = []; if( el[el.length-1] == e ) last = typeExpr(e, withType); else typeExpr(e, NoValue); } } for( f in delayed ) last = f(); return last; default: } return typeExpr(expr,withType); } inline function edef( e : Expr ) { #if hscriptPos return e.e; #else return e; #end } inline function error( msg : String, curExpr : Expr ) { var e = ECustom(msg); #if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end if( !isCompletion ) throw e; } function saveLocals() { return [for( k in locals.keys() ) k => locals.get(k)]; } function makeType( t : CType, e : Expr ) : TType { return switch (t) { case CTPath(path, params): var ct = types.resolve(path.join("."),params == null ? [] : [for( p in params ) makeType(p,e)]); if( ct == null ) { error("Unknown type "+path, e); ct = TDynamic; } return ct; case CTFun(args, ret): var i = 0; return TFun([for( a in args ) { name : "p"+(i++), opt : false, t : makeType(a,e) }], makeType(ret,e)); case CTAnon(fields): return TAnon([for( f in fields ) { name : f.name, opt : false, t : makeType(f.t,e) }]); case CTParent(t): return makeType(t,e); case CTNamed(n, t): return makeType(t,e); case CTOpt(t): return makeType(t,e); } } public static function typeStr( t : TType ) { inline function makeArgs(args:Array) return args.length==0 ? "" : "<"+[for( t in args ) typeStr(t)].join(",")+">"; return switch (t) { case TMono(r): r.r == null ? "Unknown" : typeStr(r.r); case TInst(c, args): c.name + makeArgs(args); case TEnum(e, args): e.name + makeArgs(args); case TType(t, args): if( t.name == "hscript.TypeCheck" ) typeStr(args[1]); else t.name + makeArgs(args); case TAbstract(a, args): a.name + makeArgs(args); case TFun(args, ret): "(" + [for( a in args ) (a.opt?"?":"")+(a.name == "" ? "" : a.name+":")+typeStr(a.t)].join(", ")+") -> "+typeStr(ret); case TAnon(fields): "{" + [for( f in fields ) (f.opt?"?":"")+f.name+":"+typeStr(f.t)].join(", ")+"}"; case TParam(name): name; case TNull(t): "Null<"+typeStr(t)+">"; case TUnresolved(name): "?"+name; default: t.getName().substr(1); } } function linkLoop( a : TType, t : TType ) { if( t == a ) return true; switch( t ) { case TMono(r): if( r.r == null ) return false; return linkLoop(a,r.r); case TEnum(_,tl), TInst(_,tl), TType(_,tl), TAbstract(_,tl): for( t in tl ) if( linkLoop(a,t) ) return true; return false; case TFun(args,ret): for( arg in args ) if( linkLoop(a,arg.t) ) return true; return linkLoop(a,ret); case TDynamic: if( t == TDynamic ) return false; return linkLoop(a,TDynamic); case TAnon(fl): for( f in fl ) if( linkLoop(a, f.t) ) return true; return false; default: return false; } } function link( a : TType, b : TType, r : { r : TType } ) { if( linkLoop(a,b) ) return follow(b) == a; if( b == TDynamic ) return true; r.r = b; return true; } function typeEq( t1 : TType, t2 : TType ) { if( t1 == t2 ) return true; switch( [t1,t2] ) { case [TMono(r), _]: if( r.r == null ) { if( !link(t1,t2,r) ) return false; r.r = t2; return true; } return typeEq(r.r, t2); case [_, TMono(r)]: if( r.r == null ) { if( !link(t2,t1,r) ) return false; r.r = t1; return true; } return typeEq(t1, r.r); case [TType(t1,pl1),TType(t2,pl2)] if( t1 == t2 ): for( i in 0...pl1.length ) if( !typeEq(pl1[i],pl2[i]) ) return false; return true; case [TType(t1,pl1), _]: return typeEq(apply(t1.t, t1.params, pl1), t2); case [_,TType(t2,pl2)]: return typeEq(t1, apply(t2.t, t2.params, pl2)); case [TInst(cl1,pl1), TInst(cl2,pl2)] if( cl1 == cl2 ): for( i in 0...pl1.length ) if( !typeEq(pl1[i],pl2[i]) ) return false; return true; case [TEnum(e1,pl1), TEnum(e2,pl2)] if( e1 == e2 ): for( i in 0...pl1.length ) if( !typeEq(pl1[i],pl2[i]) ) return false; return true; case [TAbstract(a1,pl1), TAbstract(a2,pl2)] if( a1 == a2 ): for( i in 0...pl1.length ) if( !typeEq(pl1[i],pl2[i]) ) return false; return true; case [TNull(t1), TNull(t2)]: return typeEq(t1,t2); case [TNull(t1), _]: return typeEq(t1,t2); case [_, TNull(t2)]: return typeEq(t1,t2); case [TFun(args1,r1), TFun(args2,r2)] if( args1.length == args2.length ): for( i in 0...args1.length ) if( !typeEq(args1[i].t, args2[i].t) ) return false; return typeEq(r1, r2); case [TAnon(a1),TAnon(a2)] if( a1.length == a2.length ): var m = new Map(); for( f in a2 ) m.set(f.name, f); for( f1 in a1 ) { var f2 = m.get(f1.name); if( f2 == null ) return false; if( !typeEq(f1.t,f2.t) ) return false; } return true; default: } return false; } function tryUnify( t1 : TType, t2 : TType ) { if( t1 == t2 ) return true; switch( [t1,t2] ) { case [TMono(r), _]: if( r.r == null ) { if( !link(t1,t2,r) ) return false; r.r = t2; return true; } return tryUnify(r.r, t2); case [_, TMono(r)]: if( r.r == null ) { if( !link(t2,t1,r) ) return false; r.r = t1; return true; } return tryUnify(t1, r.r); case [TType(t1,pl1), _]: return tryUnify(apply(t1.t, t1.params, pl1), t2); case [_,TType(t2,pl2)]: return tryUnify(t1, apply(t2.t, t2.params, pl2)); case [TNull(t1), _]: return tryUnify(t1,t2); case [_, TNull(t2)]: return tryUnify(t1,t2); case [TFun(args1,r1),TFun(args2,r2)] if( args1.length == args2.length ): for( i in 0...args1.length ) { var a1 = args1[i]; var a2 = args2[i]; if( a2.opt && !a1.opt ) return false; if( !tryUnify(a2.t, a1.t) ) return false; } return tryUnify(r1,r2); case [_, TDynamic]: return true; case [TDynamic, _]: return true; case [TAnon(a1),TAnon(a2)]: if( a2.length == 0 ) // always unify with {} return true; var m = new Map(); for( f in a1 ) m.set(f.name, f); for( f2 in a2 ) { var f1 = m.get(f2.name); if( f1 == null ) { if( f2.opt ) continue; return false; } if( !typeEq(f1.t,f2.t) ) return false; } return true; case [TInst(cl1,pl1), TInst(cl2,pl2)]: while( cl1 != cl2 ) { if( cl1.interfaces != null ) { for( i in cl1.interfaces ) { switch( i ) { case TInst(cli, args): var i = TInst(cli, [for( a in args ) apply(a, cl1.params, pl1)]); if( tryUnify(i, t2) ) return true; default: throw "assert"; } } } switch( cl1.superClass ) { case null: return false; case TInst(c, args): pl1 = [for( a in args ) apply(a,cl1.params,pl1)]; cl1 = c; default: throw "assert"; } } for( i in 0...pl1.length ) if( !typeEq(pl1[i],pl2[i]) ) return false; return true; case [TInst(cl1,pl1),TAnon(fl)]: for( i in 0...fl.length ) { var f2 = fl[i]; var f1 = null; var cl = cl1; while( true ) { f1 = cl.fields.get(f2.name); if( f1 != null ) break; if( cl.superClass == null ) return false; cl = switch( cl.superClass ) { case TInst(c,_): c; default: throw "assert"; } } if( !typeEq(f1.t,f2.t) ) return false; } return true; case [TInt, TFloat]: return true; case [TFun(_), TAbstract({ name : "haxe.Function" },_)]: return true; default: } return typeEq(t1,t2); } public function unify( t1 : TType, t2 : TType, e : Expr ) { if( !tryUnify(t1,t2) ) error(typeStr(t1)+" should be "+typeStr(t2),e); } public function apply( t : TType, params : Array, args : Array ) { if( args.length != params.length ) throw "Invalid number of type parameters"; if( args.length == 0 ) return t; var subst = new Map(); for( i in 0...params.length ) subst.set(params[i], args[i]); function map(t:TType) { var st = subst.get(t); if( st != null ) return st; return mapType(t,map); } return map(t); } public function mapType( t : TType, f : TType -> TType ) { switch (t) { case TMono(r): if( r.r == null ) return t; return f(t); case TVoid, TInt, TFloat,TBool,TDynamic,TParam(_), TUnresolved(_): return t; case TEnum(_,[]), TInst(_,[]), TAbstract(_,[]), TType(_,[]): return t; case TNull(t): return TNull(f(t)); case TInst(c, args): return TInst(c, [for( t in args ) f(t)]); case TEnum(e, args): return TEnum(e, [for( t in args ) f(t)]); case TType(t, args): return TType(t, [for( t in args ) f(t)]); case TAbstract(a, args): return TAbstract(a, [for( t in args ) f(t)]); case TFun(args, ret): return TFun([for( a in args ) { name : a.name, opt : a.opt, t : f(a.t) }], f(ret)); case TAnon(fields): return TAnon([for( af in fields ) { name : af.name, opt : af.opt, t : f(af.t) }]); case TLazy(l): return f(l()); } } public function follow( t : TType ) { return switch( t ) { case TMono(r): if( r.r != null ) follow(r.r) else t; case TType(t,args): follow(apply(t.t, t.params, args)); case TNull(t): follow(t); case TLazy(f): follow(f()); default: t; } } public function getFields( t : TType ) : Array<{ name : String, t : TType }> { var fields = []; switch( follow(t) ) { case TInst(c, args): var map = (t) -> apply(t,c.params,args); while( c != null ) { for( fname in c.fields.keys() ) { var f = c.fields.get(fname); if( !f.isPublic || !f.complete ) continue; var name = f.name, t = map(f.t); if( allowAsync && StringTools.startsWith(name,"a_") ) { t = unasync(t); name = name.substr(2); } fields.push({ name : name, t : t }); } if( c.isInterface && c.interfaces != null ) { for( i in c.interfaces ) { for( f in getFields(i) ) fields.push({ name : f.name, t : map(f.t) }); } } if( c.superClass == null ) break; switch( c.superClass ) { case TInst(csup,args): var curMap = map; map = (t) -> curMap(apply(t,csup.params,args)); c = csup; default: break; } } case TAnon(fl): for( f in fl ) fields.push({ name : f.name, t : f.t }); default: } return fields; } function getField( t : TType, f : String, e : Expr, forWrite = false ) { switch( follow(t) ) { case TInst(c, args): var cf = c.fields.get(f); if( cf == null && allowAsync ) { cf = c.fields.get("a_"+f); if( cf != null ) { var isPublic = true; // consider a_ prefixed as script specific cf = { isPublic : isPublic, canWrite : false, params : cf.params, name : cf.name, t : unasync(cf.t), complete : cf.complete }; if( cf.t == null ) cf = null; } } if( cf == null && c.isInterface && c.interfaces != null ) { for( i in c.interfaces ) { var ft = getField(i, f, e, forWrite); if( ft != null ) return apply(ft, c.params, args); } } if( cf == null ) { if( c.superClass == null ) return null; var ft = getField(c.superClass, f, e, forWrite); if( ft != null ) ft = apply(ft, c.params, args); return ft; } if( !cf.isPublic ) error("Can't access private field "+f+" on "+c.name, e); if( forWrite && !cf.canWrite ) error("Can't write readonly field "+f+" on "+c.name, e); var t = cf.t; if( cf.params != null ) t = apply(t, cf.params, [for( i in 0...cf.params.length ) makeMono()]); return apply(t, c.params, args); case TDynamic: return makeMono(); case TAnon(fields): for( af in fields ) if( af.name == f ) return af.t; return null; default: return null; } } public function unasync( t : TType ) : TType { switch( follow(t) ) { case TFun(args, ret) if( args.length > 0 ): var rargs = args.copy(); switch( follow(rargs.shift().t) ) { case TFun([r],_): return TFun(rargs,r.t); default: } default: } return null; } function typeExprWith( expr : Expr, t : TType ) { var et = typeExpr(expr, WithType(t)); unify(et, t, expr); return t; } function makeMono() { return TMono({r:null}); } function makeIterator(t) : TType { return TAnon([{ name : "next", opt : false, t : TFun([],t) }, { name : "hasNext", opt : false, t : TFun([],TBool) }]); } function mk(e,p) : Expr { #if hscriptPos return { e : e, pmin : p.pmin, pmax : p.pmax, origin : p.origin, line : p.line }; #else return e; #end } function isString( t : TType ) { t = follow(t); return t.match(TInst({name:"String"},_)); } function onCompletion( expr : Expr, t : TType ) { if( isCompletion ) throw new Completion(expr, t); } function typeField( o : Expr, f : String, expr : Expr, forWrite : Bool ) { var ot = typeExpr(o, Value); if( f == null ) onCompletion(expr, ot); var ft = getField(ot, f, expr, forWrite); if( ft == null ) { error(typeStr(ot)+" has no field "+f, expr); ft = TDynamic; } return ft; } function typeExpr( expr : Expr, withType : WithType ) : TType { if( expr == null && isCompletion ) return switch( withType ) { case WithType(t): t; default: TDynamic; } switch( edef(expr) ) { case EConst(c): return switch (c) { case CInt(_): TInt; case CFloat(_): TFloat; case CString(_): types.t_string; } case EIdent(v): var l = locals.get(v); if( l != null ) return l; var g = globals.get(v); if( g != null ) { return switch( g ) { case TLazy(f): f(); default: g; } } if( allowAsync ) { g = globals.get("a_"+v); if( g != null ) g = unasync(g); if( g != null ) return g; } switch( v ) { case "null": return makeMono(); case "true", "false": return TBool; case "trace": return TDynamic; default: if( isCompletion) return TDynamic; error("Unknown identifier "+v, expr); } case EBlock(el): var t = TVoid; var locals = saveLocals(); for( e in el ) t = typeExpr(e, e == el[el.length-1] ? withType : NoValue); this.locals = locals; return t; case EVar(n, t, init): var vt = t == null ? makeMono() : makeType(t, expr); if( init != null ) { var et = typeExpr(init, t == null ? Value : WithType(vt)); if( t == null ) vt = et else unify(et,vt, init); } locals.set(n, vt); return TVoid; case EParent(e): return typeExpr(e,withType); case ECall(e, params): var ft = typeExpr(e, Value); switch( follow(ft) ) { case TFun(args, ret): for( i in 0...params.length ) { var a = args[i]; if( a == null ) { error("Too many arguments", params[i]); break; } var t = typeExpr(params[i], a == null ? Value : WithType(a.t)); unify(t, a.t, params[i]); } for( i in params.length...args.length ) if( !args[i].opt ) error("Missing argument "+args[i].name+":"+typeStr(args[i].t), expr); return ret; case TDynamic: for( p in params ) typeExpr(p,Value); return makeMono(); default: error(typeStr(ft)+" cannot be called", e); return makeMono(); } case EField(o, f): return typeField(o,f,expr,false); case ECheckType(v, t): var ct = makeType(t, expr); var vt = typeExpr(v, WithType(ct)); unify(vt, ct, v); return ct; case EMeta(m, _, e): if( m == ":untyped" && allowUntypedMeta ) return makeMono(); return typeExpr(e, withType); case EIf(cond, e1, e2), ETernary(cond, e1, e2): typeExprWith(cond, TBool); var t1 = typeExpr(e1, withType); if( e2 == null ) return t1; var t2 = typeExpr(e2, withType); if( withType == NoValue ) return TVoid; if( tryUnify(t2,t1) ) return t1; if( tryUnify(t1,t2) ) return t2; unify(t2,t1,e2); // error case EWhile(cond, e), EDoWhile(cond, e): typeExprWith(cond,TBool); typeExpr(e, NoValue); return TVoid; case EObject(fl): switch( withType ) { case WithType(follow(_) => TAnon(tfields)) if( tfields.length > 0 ): var map = [for( f in tfields ) f.name => f]; return TAnon([for( f in fl ) { var ft = map.get(f.name); var ft = if( ft == null ) { error("Extra field "+f.name, f.e); TDynamic; } else ft.t; { t : typeExprWith(f.e, ft), opt : false, name : f.name } }]); default: return TAnon([for( f in fl ) { t : typeExpr(f.e, Value), opt : false, name : f.name }]); } case EBreak, EContinue, EImport(_): return TVoid; case EReturn(v): var et = v == null ? TVoid : typeExpr(v, allowReturn == null ? Value : WithType(allowReturn)); if( allowReturn == null ) error("Return not allowed here", expr); else unify(et, allowReturn, v == null ? expr : v); return makeMono(); case EArrayDecl(el): var et = null; for( v in el ) { var t = typeExpr(v, et == null ? Value : WithType(et)); if( et == null ) et = t else if( !tryUnify(t,et) ) { if( tryUnify(et,t) ) et = t else unify(t,et,v); } } if( et == null ) et = makeMono(); return types.getType("Array",[et]); case EArray(a, index): typeExprWith(index, TInt); var at = typeExpr(a, Value); switch( follow(at) ) { case TInst({ name : "Array"},[et]): return et; default: error(typeStr(at)+" is not an Array", a); } case EThrow(e): typeExpr(e, Value); return makeMono(); case EFunction(args, body, name, ret): var ft = null, tret = null, targs = null; if( currentFunType != null ) { switch( currentFunType ) { case TFun(args,ret): ft = currentFunType; tret = ret; targs = args; default: throw "assert"; } currentFunType = null; } else { tret = ret == null ? makeMono() : makeType(ret, expr); } var locals = saveLocals(); var oldRet = allowReturn; var oldGDef = allowDefine; allowReturn = tret; allowDefine = false; var withArgs = null; if( name != null && !withType.match(WithType(follow(_) => TFun(_))) ) { var ev = events.get(name); if( ev != null ) withType = WithType(ev); } switch( withType ) { case WithType(follow(_) => TFun(args,ret)): withArgs = args; unify(tret,ret,expr); default: } if( targs == null ) targs = typeArgs(args,expr); for( i in 0...targs.length ) { var a = targs[i]; if( withArgs != null ) { if( i < withArgs.length ) unify(withArgs[i].t, a.t, expr); else error("Extra argument "+a.name, expr); } this.locals.set(a.name, a.t); } if( withArgs != null && targs.length < withArgs.length ) error("Missing "+(withArgs.length - targs.length)+" arguments ("+[for( i in targs.length...withArgs.length ) typeStr(withArgs[i].t)].join(",")+")", expr); typeExpr(body,NoValue); allowDefine = oldGDef; allowReturn = oldRet; this.locals = locals; if( ft == null ) { ft = TFun(targs, tret); locals.set(name, ft); } return ft; case EUnop(op, _, e): var et = typeExpr(e, Value); switch( op ) { case "++", "--", "-": unify(et,TInt,e); return et; case "!": unify(et,TBool,e); return et; default: } case EFor(v, it, e): var locals = saveLocals(); var itt = typeExpr(it, Value); var vt = switch( follow(itt) ) { case TInst({name:"Array"},[t]): t; default: var ft = getField(itt,"iterator", it); if( ft == null ) switch( itt ) { case TAbstract(a, args): // special case : we allow unconditional access // to an abstract iterator() underlying value (eg: ArrayProxy) ft = getField(apply(a.t,a.params,args),"iterator",it); default: } if( ft != null ) switch( ft ) { case TFun([],ret): ft = ret; default: ft = null; } var t = makeMono(); var iter = makeIterator(t); unify(ft != null ? ft : itt,iter,it); t; } this.locals.set(v, vt); typeExpr(e, NoValue); this.locals = locals; return TVoid; case EBinop(op, e1, e2): switch( op ) { case "&", "|", "^", ">>", ">>>", "<<": typeExprWith(e1,TInt); typeExprWith(e2,TInt); return TInt; case "=": if( allowDefine ) { switch( edef(e1) ) { case EIdent(i) if( !locals.exists(i) && !globals.exists(i) ): var vt = typeExpr(e2,Value); locals.set(i, vt); return vt; default: } } var vt = switch( edef(e1) ) { case EField(o,f): typeField(o, f, e1, true); default: typeExpr(e1,Value); } typeExprWith(e2,vt); return vt; case "+": var t1 = typeExpr(e1,WithType(TInt)); var t2 = typeExpr(e2,WithType(t1)); tryUnify(t1,t2); switch( [follow(t1), follow(t2)]) { case [TInt, TInt]: return TInt; case [TFloat, TInt], [TInt, TFloat], [TFloat, TFloat]: return TFloat; case [TDynamic, _], [_, TDynamic]: return TDynamic; case [t1,t2]: if( isString(t1) || isString(t2) ) return types.t_string; unify(t1, TFloat, e1); unify(t2, TFloat, e2); } case "-", "*", "/", "%": var t1 = typeExpr(e1,WithType(TInt)); var t2 = typeExpr(e2,WithType(t1)); if( !tryUnify(t1,t2) ) unify(t2,t1,e2); switch( [follow(t1), follow(t2)]) { case [TInt, TInt]: if( op == "/" ) return TFloat; return TInt; case [TFloat|TDynamic, TInt|TDynamic], [TInt|TDynamic, TFloat|TDynamic], [TFloat, TFloat]: return TFloat; default: unify(t1, TFloat, e1); unify(t2, TFloat, e2); } case "&&", "||": typeExprWith(e1,TBool); typeExprWith(e2,TBool); return TBool; case "...": typeExprWith(e1,TInt); typeExprWith(e2,TInt); return makeIterator(TInt); case "==", "!=": var t1 = typeExpr(e1,Value); var t2 = typeExpr(e2,WithType(t1)); if( !tryUnify(t1,t2) ) unify(t2,t1,e2); return TBool; case ">", "<", ">=", "<=": var t1 = typeExpr(e1,Value); var t2 = typeExpr(e2,WithType(t1)); if( !tryUnify(t1,t2) ) unify(t2,t1,e2); switch( follow(t1) ) { case TInt, TFloat, TBool, TInst({name:"String"},_): default: error("Cannot compare "+typeStr(t1), expr); } return TBool; default: if( op.charCodeAt(op.length-1) == "=".code ) { var t = typeExpr(mk(EBinop(op.substr(0,op.length-1),e1,e2),expr),withType); return typeExpr(mk(EBinop("=",e1,e2),expr), withType); } error("Unsupported operation "+op, expr); } case ETry(etry, v, et, ecatch): var vt = typeExpr(etry, withType); var old = locals.get(v); locals.set(v, makeType(et, ecatch)); var ct = typeExpr(ecatch, withType); if( old != null ) locals.set(v,old) else locals.remove(v); if( withType == NoValue ) return TVoid; if( tryUnify(vt,ct) ) return ct; unify(ct,vt,ecatch); return vt; case ESwitch(value, cases, defaultExpr): var tmin = null; var vt = typeExpr(value, Value); inline function mergeType(t,p) { if( withType != NoValue ) { if( tmin == null ) tmin = t; else if( !tryUnify(t,tmin) ) { unify(tmin,t, p); tmin = t; } } } for( c in cases ) { for( v in c.values ) { var ct = typeExpr(v, WithType(vt)); unify(ct, vt, v); } var et = typeExpr(c.expr, withType); mergeType(et, c.expr); } if( defaultExpr != null ) mergeType( typeExpr(defaultExpr, withType), defaultExpr); return withType == NoValue ? TVoid : tmin == null ? makeMono() : tmin; case ENew(cl, params): } error("Don't know how to type "+edef(expr).getName(), expr); return TDynamic; } }