Files
hscript-improved/hscript/Checker.hx
T
LeerandGitHub 7dd868aac7 fix compilation errors and make setVar public (#1)
* fix 1

* fix 2

* make setVar public
2023-09-23 20:32:31 +02:00

1243 lines
34 KiB
Haxe

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,Script> = 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<TType> );
TEnum( e : CEnum, args : Array<TType> );
TType( t : CTypedef, args : Array<TType> );
TAbstract( a : CAbstract, args : Array<TType> );
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<TType>;
}
typedef CClass = {> CNamedType,
@:optional var superClass : TType;
@:optional var constructor : CField;
@:optional var interfaces : Array<TType>;
@:optional var isInterface : Bool;
var fields : Map<String,CField>;
var statics : Map<String,CField>;
}
typedef CField = {
var isPublic : Bool;
var canWrite : Bool;
var complete : Bool;
var params : Array<TType>;
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<String,CTypedecl> = new Map();
var t_string : TType;
var localParams : Map<String,TType>;
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:Array<Void->Void>) {
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> ) : 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> ) : TType {
if( name == "Null" ) {
if( args == null || args.length != 1 ) throw "Missing Null<T> 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<String,TType>;
var globals : Map<String,TType> = new Map();
var events : Map<String,TType> = new Map();
var currentFunType : TType;
var isCompletion : Bool;
var allowDefine : Bool;
public var allowAsync : Bool;
public var allowReturn : Null<TType>;
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<Argument>, 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<TType>) 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<TType>, args : Array<TType> ) {
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):
default:
}
error("Don't know how to type "+edef(expr).getName(), expr);
return TDynamic;
}
}