Deal with null monos.

This commit is contained in:
Juraj Kirchheim
2019-02-24 08:24:50 +01:00
parent 1b717919fa
commit 8f81256731

View File

@@ -1,275 +1,275 @@
package tink.macro; package tink.macro;
import haxe.macro.Printer; import haxe.macro.Printer;
import haxe.macro.Context; import haxe.macro.Context;
import haxe.macro.Expr; import haxe.macro.Expr;
import haxe.macro.Type; import haxe.macro.Type;
using haxe.macro.Tools; using haxe.macro.Tools;
using tink.MacroApi; using tink.MacroApi;
using tink.CoreApi; using tink.CoreApi;
class Types { class Types {
static public function definedType(typeName:String) static public function definedType(typeName:String)
return return
try { try {
Some(Context.getType(typeName)); Some(Context.getType(typeName));
} }
catch (e:Dynamic) catch (e:Dynamic)
if (Std.string(e) == 'Type not found \'$typeName\'') None; if (Std.string(e) == 'Type not found \'$typeName\'') None;
else tink.core.Error.rethrow(e); else tink.core.Error.rethrow(e);
static var types = new Map<Int,Void->Type>(); static var types = new Map<Int,Void->Type>();
static var idCounter = 0; static var idCounter = 0;
static public function getID(t:Type, ?reduced = true) static public function getID(t:Type, ?reduced = true)
return return
if (reduced) if (reduced)
getID(reduce(t), false); getID(reduce(t), false);
else else
switch (t) { switch (t) {
case TAbstract(t, _): t.toString(); case TAbstract(t, _): t.toString();
case TInst(t, _): t.toString(); case TInst(t, _): t.toString();
case TEnum(t, _): t.toString(); case TEnum(t, _): t.toString();
case TType(t, _): t.toString(); case TType(t, _): t.toString();
default: null; default: null;
} }
static public function accessToName(v:VarAccess, ?read = true) static public function accessToName(v:VarAccess, ?read = true)
return return
switch (v) { switch (v) {
case AccNormal, AccInline: 'default'; case AccNormal, AccInline: 'default';
case AccNo: 'null'; case AccNo: 'null';
case AccNever: 'never'; case AccNever: 'never';
case AccCall: if (read) 'get' else 'set'; case AccCall: if (read) 'get' else 'set';
default: default:
throw 'not implemented'; throw 'not implemented';
} }
static public function getMeta(type:Type) static public function getMeta(type:Type)
return switch type { return switch type {
case TInst(_.get().meta => m, _): [m]; case TInst(_.get().meta => m, _): [m];
case TEnum(_.get().meta => m, _): [m]; case TEnum(_.get().meta => m, _): [m];
case TAbstract(_.get().meta => m, _): [m]; case TAbstract(_.get().meta => m, _): [m];
case TType(_.get() => t, _): [t.meta].concat(getMeta(t.type)); case TType(_.get() => t, _): [t.meta].concat(getMeta(t.type));
case TLazy(f): getMeta(f()); case TLazy(f): getMeta(f());
default: []; default: [];
} }
static function getDeclaredFields(t:ClassType, out:Array<ClassField>, marker:Map<String,Bool>) { static function getDeclaredFields(t:ClassType, out:Array<ClassField>, marker:Map<String,Bool>) {
for (field in t.fields.get()) for (field in t.fields.get())
if (!marker.exists(field.name)) { if (!marker.exists(field.name)) {
marker.set(field.name, true); marker.set(field.name, true);
out.push(field); out.push(field);
} }
if (t.isInterface) if (t.isInterface)
for (t in t.interfaces) for (t in t.interfaces)
getDeclaredFields(t.t.get(), out, marker); getDeclaredFields(t.t.get(), out, marker);
else if (t.superClass != null) else if (t.superClass != null)
getDeclaredFields(t.superClass.t.get(), out, marker); getDeclaredFields(t.superClass.t.get(), out, marker);
} }
static var fieldsCache = new Map(); static var fieldsCache = new Map();
static public function getFields(t:Type, ?substituteParams = true) static public function getFields(t:Type, ?substituteParams = true)
return return
switch (reduce(t)) { switch (reduce(t)) {
case TInst(c, params): case TInst(c, params):
var id = c.toString(), var id = c.toString(),
c = c.get(); c = c.get();
if (!fieldsCache.exists(id)) { if (!fieldsCache.exists(id)) {
var fields = []; var fields = [];
getDeclaredFields(c, fields, new Map()); getDeclaredFields(c, fields, new Map());
fieldsCache.set(id, Success(fields)); fieldsCache.set(id, Success(fields));
} }
var ret = fieldsCache.get(id); var ret = fieldsCache.get(id);
if (substituteParams && ret.isSuccess()) { if (substituteParams && ret.isSuccess()) {
var fields = Reflect.copy(ret.sure()); var fields = Reflect.copy(ret.sure());
for (field in fields) for (field in fields)
field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params); field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params);
} }
fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache
ret; ret;
case TAnonymous(anon): Success(anon.get().fields); case TAnonymous(anon): Success(anon.get().fields);
default: Context.currentPos().makeFailure('type $t has no fields'); default: Context.currentPos().makeFailure('type $t has no fields');
} }
static public function getStatics(t:Type) static public function getStatics(t:Type)
return return
switch (reduce(t)) { switch (reduce(t)) {
case TInst(t, _): Success(t.get().statics.get()); case TInst(t, _): Success(t.get().statics.get());
default: Failure('type has no statics'); default: Failure('type has no statics');
} }
static public function getPosition(t:Type) static public function getPosition(t:Type)
return return
switch t { switch t {
case TInst(_.get() => {pos: pos}, _) case TInst(_.get() => {pos: pos}, _)
| TAbstract(_.get() => {pos: pos}, _) | TAbstract(_.get() => {pos: pos}, _)
| TType(_.get() => {pos: pos}, _) | TType(_.get() => {pos: pos}, _)
| TEnum(_.get() => {pos: pos}, _) : Success(pos); | TEnum(_.get() => {pos: pos}, _) : Success(pos);
case TMono(ref): getPosition(ref.get()); case TMono(_.get() => t) if (t != null): getPosition(t);
case TLazy(f): getPosition(f()); case TLazy(f): getPosition(f());
case TDynamic(v) if(v != null): getPosition(v); case TDynamic(v) if(v != null): getPosition(v);
default: Failure('type "$t" has no position'); default: Failure('type "$t" has no position');
} }
static public function toString(t:ComplexType) static public function toString(t:ComplexType)
return new Printer().printComplexType(t); return new Printer().printComplexType(t);
static public function isSubTypeOf(t:Type, of:Type, ?pos) static public function isSubTypeOf(t:Type, of:Type, ?pos)
return return
ECheckType(ECheckType(macro null, toComplex(t)).at(pos), toComplex(of)).at(pos).typeof(); ECheckType(ECheckType(macro null, toComplex(t)).at(pos), toComplex(of)).at(pos).typeof();
static public function isDynamic(t:Type) static public function isDynamic(t:Type)
return switch reduce(t) { return switch reduce(t) {
case TDynamic(_): true; case TDynamic(_): true;
default: false; default: false;
} }
static public function toType(t:ComplexType, ?pos:Position) static public function toType(t:ComplexType, ?pos:Position)
return (macro @:pos(pos.sanitize()) { return (macro @:pos(pos.sanitize()) {
var v:$t = null; var v:$t = null;
v; v;
}).typeof(); }).typeof();
static public inline function instantiate(t:TypePath, ?args, ?pos) static public inline function instantiate(t:TypePath, ?args, ?pos)
return ENew(t, args == null ? [] : args).at(pos); return ENew(t, args == null ? [] : args).at(pos);
static public function asTypePath(s:String, ?params):TypePath { static public function asTypePath(s:String, ?params):TypePath {
var parts = s.split('.'); var parts = s.split('.');
var name = parts.pop(), var name = parts.pop(),
sub = null; sub = null;
if (parts.length > 0 && parts[parts.length - 1].charCodeAt(0) < 0x5B) { if (parts.length > 0 && parts[parts.length - 1].charCodeAt(0) < 0x5B) {
sub = name; sub = name;
name = parts.pop(); name = parts.pop();
if(sub == name) sub = null; if(sub == name) sub = null;
} }
return { return {
name: name, name: name,
pack: parts, pack: parts,
params: params == null ? [] : params, params: params == null ? [] : params,
sub: sub sub: sub
}; };
} }
static public inline function asComplexType(s:String, ?params) static public inline function asComplexType(s:String, ?params)
return TPath(asTypePath(s, params)); return TPath(asTypePath(s, params));
static public function reduce(type:Type, ?once) { static public function reduce(type:Type, ?once) {
function rec(t:Type) function rec(t:Type)
return if (once) t else reduce(t, false); return if (once) t else reduce(t, false);
return switch type { return switch type {
case TAbstract(_.get() => { name: 'Null', pack: [] }, [t]): rec(t); case TAbstract(_.get() => { name: 'Null', pack: [] }, [t]): rec(t);
case TLazy(f): rec(f()); case TLazy(f): rec(f());
case TType(_, _): rec(Context.follow(type, once)); case TType(_, _): rec(Context.follow(type, once));
default: type; default: type;
} }
} }
static public function isVar(field:ClassField) static public function isVar(field:ClassField)
return switch (field.kind) { return switch (field.kind) {
case FVar(_, _): true; case FVar(_, _): true;
default: false; default: false;
} }
static public function register(type:Void->Type):Int { static public function register(type:Void->Type):Int {
types.set(idCounter, type); types.set(idCounter, type);
return idCounter++; return idCounter++;
} }
static function paramsToComplex(params:Array<Type>):Array<TypeParam> static function paramsToComplex(params:Array<Type>):Array<TypeParam>
return [for (p in params) TPType(toComplex(p))]; return [for (p in params) TPType(toComplex(p))];
static function baseToComplex(t:BaseType, params:Array<Type>) static function baseToComplex(t:BaseType, params:Array<Type>)
return asComplexType(t.module + '.' + t.name, paramsToComplex(params)); return asComplexType(t.module + '.' + t.name, paramsToComplex(params));
static public function toComplex(type:Type, ?options:{ ?direct: Bool }):ComplexType { static public function toComplex(type:Type, ?options:{ ?direct: Bool }):ComplexType {
var ret = var ret =
if (options == null || options.direct != true) tink.macro.Sisyphus.toComplexType(type); if (options == null || options.direct != true) tink.macro.Sisyphus.toComplexType(type);
else null; else null;
if (ret == null) if (ret == null)
ret = lazyComplex(function () return type); ret = lazyComplex(function () return type);
return ret; return ret;
} }
static public function intersect(types:Array<ComplexType>, ?pos:Position):Outcome<ComplexType, Error> { static public function intersect(types:Array<ComplexType>, ?pos:Position):Outcome<ComplexType, Error> {
if (types.length == 1) return Success(types[1]); if (types.length == 1) return Success(types[1]);
var paths = [], var paths = [],
fields = []; fields = [];
for (t in types) for (t in types)
switch t { switch t {
case TPath(p): paths.push(p); case TPath(p): paths.push(p);
case TAnonymous(f): case TAnonymous(f):
for (f in f) fields.push(f); for (f in f) fields.push(f);
case TExtend(p, f): case TExtend(p, f):
for (f in f) fields.push(f); for (f in f) fields.push(f);
for (p in p) paths.push(p); for (p in p) paths.push(p);
default: default:
return Failure(new Error(t.toString() + ' cannot be interesected', pos)); return Failure(new Error(t.toString() + ' cannot be interesected', pos));
} }
return Success(TExtend(paths, fields)); return Success(TExtend(paths, fields));
} }
static public function lazyComplex(f:Void->Type) static public function lazyComplex(f:Void->Type)
return return
TPath({ TPath({
pack : ['tink','macro'], pack : ['tink','macro'],
name : 'DirectType', name : 'DirectType',
params : [TPExpr(register(f).toExpr())], params : [TPExpr(register(f).toExpr())],
sub : null, sub : null,
}); });
static function resolveDirectType() static function resolveDirectType()
return return
switch reduce(Context.getLocalType()) { switch reduce(Context.getLocalType()) {
case TInst(_, [TInst(_.get() => { kind: KExpr(e) }, _)]): case TInst(_, [TInst(_.get() => { kind: KExpr(e) }, _)]):
types[e.getInt().sure()]();//When using compiler server, this call throws on occasion, in which case modifying this file (to update mtime and invalidate the cache) will solve the problem types[e.getInt().sure()]();//When using compiler server, this call throws on occasion, in which case modifying this file (to update mtime and invalidate the cache) will solve the problem
default: default:
throw 'assert'; throw 'assert';
} }
static public function compare(t1:Type, t2:Type, ?follow:Bool = true) { static public function compare(t1:Type, t2:Type, ?follow:Bool = true) {
if (follow) { if (follow) {
t1 = t1.reduce(); t1 = t1.reduce();
t2 = t2.reduce(); t2 = t2.reduce();
} }
return switch t1.getIndex() - t2.getIndex() { return switch t1.getIndex() - t2.getIndex() {
case 0: case 0:
Reflect.compare(t1.toString(), t2.toString());//much to my surprise, this actually seems to work (at least with 3.4) Reflect.compare(t1.toString(), t2.toString());//much to my surprise, this actually seems to work (at least with 3.4)
case v: v; case v: v;
} }
} }
static var SUGGESTIONS = ~/ \(Suggestions?: .*\)$/; static var SUGGESTIONS = ~/ \(Suggestions?: .*\)$/;
static public function getFieldSuggestions(type:ComplexType, name:String):String static public function getFieldSuggestions(type:ComplexType, name:String):String
return switch (macro (null : $type).$name).typeof() { return switch (macro (null : $type).$name).typeof() {
case Failure(SUGGESTIONS.match(_.message) => true): SUGGESTIONS.matched(0); case Failure(SUGGESTIONS.match(_.message) => true): SUGGESTIONS.matched(0);
default: ''; default: '';
} }
static public function toDecl(p:TypeParameter):TypeParamDecl static public function toDecl(p:TypeParameter):TypeParamDecl
return { return {
name: p.name, name: p.name,
constraints: switch p.t { constraints: switch p.t {
case TInst(_.get() => { kind: KTypeParameter(c)}, _): [for(c in c) c.toComplex()]; case TInst(_.get() => { kind: KTypeParameter(c)}, _): [for(c in c) c.toComplex()];
case _: throw 'unreachable'; case _: throw 'unreachable';
} }
} }
} }