Merge pull request #29 from haxetink/no-reflection

No reflection
This commit is contained in:
Juraj Kirchheim
2020-01-09 07:50:25 +01:00
committed by GitHub
5 changed files with 373 additions and 235 deletions

View File

@@ -1,4 +1,4 @@
{ {
"version": "e552bdc", "version": "4.0.5",
"resolveLibs": "scoped" "resolveLibs": "scoped"
} }

View File

@@ -95,10 +95,33 @@ class Constructor {
case Value(e): e; case Value(e): e;
} }
var tmp = MacroApi.tempName(); var member = owner.memberByName(name).sure(),
var member = owner.memberByName(name).sure(); initStatement = macro @:pos(pos) this.$name = $e;
if (options.bypass && member.kind.match(FProp(_, 'never' | 'set', _, _))) { var bypass = switch options.bypass {
case null:
switch member.kind {
case FProp(_, 'default' | 'null', _):
false;
#if haxe4
case FProp('default', 'never', _):
member.isFinal = true;
false;
case FProp(_, 'set', _):
member.addMeta(':isVar');
initStatement = macro @:pos(pos) @:bypassAccessor this.$name = $e;
false;
#end
case FProp(_):
true;
case FFun(_):
pos.error('cannot rebind function');
default: false;
}
case v: v;
}
if (bypass && member.kind.match(FProp(_, 'never' | 'set', _, _))) {
member.addMeta(':isVar'); member.addMeta(':isVar');
@@ -130,8 +153,14 @@ class Constructor {
macro @:pos(pos) this.$name = $e; macro @:pos(pos) this.$name = $e;
case FVar(AccNo | AccNormal, AccNever): case FVar(AccNo | AccNormal, AccNever):
setDirectly(Context.typeExpr(macro @:pos(pos) this.$name)); setDirectly(Context.typeExpr(macro @:pos(pos) this.$name));
#if haxe4
case FVar(AccCall, _):
var target = Context.storeTypedExpr(Context.typeExpr(macro @:pos(pos) @:bypassAccessor this.$name));
macro @:pos(pos) $target = $e;
#else
case FVar(AccCall, AccNever): case FVar(AccCall, AccNever):
setDirectly(fields['get_$name'].expr()); setDirectly(fields['get_$name'].expr());
#end
case FVar(_, AccCall): case FVar(_, AccCall):
setDirectly(fields['set_$name'].expr()); setDirectly(fields['set_$name'].expr());
default: default:
@@ -141,7 +170,10 @@ class Constructor {
}).bounce(), options.prepend); }).bounce(), options.prepend);
} }
else else
addStatement(macro @:pos(pos) this.$name = $e, options.prepend); addStatement(
initStatement,
options.prepend
);
} }
public inline function publish() public inline function publish()
if (isPublic == null) if (isPublic == null)

View File

@@ -1,12 +1,11 @@
package tink.macro; package tink.macro;
import Type in Inspect;
import haxe.macro.Context; import haxe.macro.Context;
import haxe.macro.Expr; import haxe.macro.Expr;
import haxe.macro.Type; import haxe.macro.Type;
import haxe.PosInfos; import haxe.PosInfos;
import haxe.macro.Printer; import haxe.macro.Printer;
import haxe.DynamicAccess as Dict;
using Lambda; using Lambda;
using StringTools; using StringTools;
@@ -20,6 +19,7 @@ typedef VarDecl = { name : String, type : ComplexType, expr : Null<Expr> };
typedef ParamSubst = { typedef ParamSubst = {
var exists(default, null):String->Bool; var exists(default, null):String->Bool;
var get(default, null):String->ComplexType; var get(default, null):String->ComplexType;
function iterator():Iterator<ComplexType>;
} }
private class Heureka { public function new() {} } private class Heureka { public function new() {} }
@@ -48,20 +48,21 @@ class Exprs {
static public inline function as(e:Expr, c:ComplexType) static public inline function as(e:Expr, c:ComplexType)
return ECheckType(e, c).at(e.pos); return ECheckType(e, c).at(e.pos);
static public function finalize(e:Expr, ?nuPos:Position, ?rules:Dynamic<String>, ?skipFields = false, ?callPos:PosInfos) { static public function finalize(e:Expr, ?nuPos:Position, ?rules:Dict<String>, ?skipFields = false, ?callPos:PosInfos) {
if (nuPos == null) if (nuPos == null)
nuPos = Context.currentPos(); nuPos = Context.currentPos();
if (rules == null) if (rules == null)
rules = { }; rules = { };
function replace(s:String)
return { function replace(s:String):String
if (Reflect.hasField(rules, s)) return switch rules[s] {
Reflect.field(rules, s) case null:
else if (s.startsWith('tmp')) { if (s.startsWith('tmp')) {
Reflect.setField(rules, s, MacroApi.tempName(s.substr(3))); rules[s] = MacroApi.tempName(s.substr(3));
replace(s); replace(s);
} }
else s; else s;
case v: v;
} }
return e.transform(function (e:Expr) { return e.transform(function (e:Expr) {
@@ -110,16 +111,15 @@ class Exprs {
static public function getPrivate(e:Expr, field:String, ?pos:Position) static public function getPrivate(e:Expr, field:String, ?pos:Position)
return macro @:pos(pos.sanitize()) @:privateAccess $e.$field; return macro @:pos(pos.sanitize()) @:privateAccess $e.$field;
static public function substitute(source:Expr, vars:Dynamic<Expr>, ?pos) static public function substitute(source:Expr, vars:Dict<Expr>, ?pos)
return return
transform(source, function (e:Expr) { transform(source, function (e:Expr) {
return return switch e {
switch (e.getIdent()) { case macro $i{name}:
case Success(name): switch vars[name] {
if (Reflect.hasField(vars, name)) case null: e;
Reflect.field(vars, name); case v: v;
else }
e;
default: e; default: e;
} }
}, pos); }, pos);
@@ -131,44 +131,68 @@ class Exprs {
default: e; default: e;
} }
static public function substParams(source:Expr, subst:ParamSubst, ?pos):Expr static public function substParams(source:Expr, subst:ParamSubst, ?pos:Position):Expr {
return crawl( if (!subst.iterator().hasNext())
source, return source;
function (e)
return switch e.expr { function replace(ct:ComplexType)
case ENew({ pack: [], name: name }, args) if (subst.exists(name)): return switch ct {
switch subst.get(name) { case TPath({ pack: [], name: name }) if (subst.exists(name)):
case TPath(p): subst.get(name);
ENew(p, args).at(e.pos); default: ct;
default: e;//TODO: report an error?
} }
case EConst(CIdent(name)) if (subst.exists(name)):
return source
.transform(
function (e) return switch e {
case macro $i{name} if (subst.exists(name)):
switch subst.get(name) { switch subst.get(name) {
case TPath({ pack: pack, name: name }): case TPath({ pack: pack, name: name }):
pack.concat([name]).drill(e.pos); pack.concat([name]).drill(e.pos);
default: e;//TODO: report an error? default: e;//TODO: report an error?
} }
default: e; default: e;
}, })
function (c:ComplexType) .mapTypes(replace);
return
switch (c) {
case TPath({ pack: [], name: name }) if (subst.exists(name)):
subst.get(name);
default: c;
} }
, pos);
static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr static public function mapTypes(e:Expr, transformer:ComplexType->ComplexType, ?pos:Position) {
return crawl(source, transformer, function (t) return t, pos); return e.transform(
function (e) return switch e {
case { expr: ENew(p, args) }:
switch TPath(p).map(transformer) {
case TPath(nu):
ENew(nu, args).at(e.pos);
case v:
pos.error(v.toString() + ' cannot be instantiated in ${e.pos}');
}
case macro cast($v, $ct):
ct = ct.map(transformer);
(macro @:pos(e.pos) cast($v, $ct));
case { expr: ECheckType(v, ct) }: // cannot use reification, because that's `EParentheses(ECheckType)` and macros can (and apparently do) generate a non-wrapped one
ECheckType(v, ct.map(transformer)).at(e.pos);
case { expr: EVars(vars) }:
EVars([for (v in vars) {
name: v.name,
type: v.type.map(transformer),
expr: v.expr,
}]).at(e.pos);
case { expr: EFunction(kind, f) }:
EFunction(kind, Functions.mapSignature(f, transformer)).at(e.pos);
default: e;
}
);
}
static function crawlArray(a:Array<Dynamic>, transformer:Expr->Expr, retyper:ComplexType-> ComplexType, pos:Position):Array<Dynamic> static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr {
return function apply(e:Expr)
if (a == null) a; return switch e {
else case null | { expr: null }: e;
[for (v in a) default: transformer(haxe.macro.ExprTools.map(e, apply));
crawl(v, transformer, retyper, pos) }
];
return apply(source);
}
static public function getIterType(target:Expr) static public function getIterType(target:Expr)
return return
@@ -201,10 +225,11 @@ class Exprs {
,ETernary(econd, eif, eelse): ,ETernary(econd, eif, eelse):
EIf(econd, rec(eif), rec(eelse)).at(e.pos); EIf(econd, rec(eif), rec(eelse)).at(e.pos);
case ESwitch(e, cases, edef): case ESwitch(e, cases, edef):
cases = Reflect.copy(cases);//not exactly pretty, but does the job ESwitch(e, [for (c in cases) {
for (c in cases) expr: rec(c.expr),
c.expr = rec(c.expr); guard: c.guard,
ESwitch(e, cases, rec(edef)).at(e.pos); values: c.values,
}], rec(edef)).at(e.pos);
case EFor(it, expr) if (loops): case EFor(it, expr) if (loops):
EFor(it, rec(expr)).at(e.pos); EFor(it, rec(expr)).at(e.pos);
case EWhile(cond, body, normal) if (loops): case EWhile(cond, body, normal) if (loops):
@@ -219,36 +244,12 @@ class Exprs {
} }
} }
static function crawl(target:Dynamic, transformer:Expr->Expr, retyper:ComplexType->ComplexType, pos:Position):Dynamic
return
if (Std.is(target, Array))
crawlArray(target, transformer, retyper, pos);
else
switch (Inspect.typeof(target)) {
case TNull, TInt, TFloat, TBool, TFunction, TUnknown, TClass(_): target;
case TEnum(e):
var ret:Dynamic = Inspect.createEnumIndex(e, Inspect.enumIndex(target), crawlArray(Inspect.enumParameters(target), transformer, retyper, pos));
if (Inspect.getEnum(ret) == ComplexType)
retyper(ret);
else
ret;
case TObject:
var ret:Dynamic = { };
for (field in Reflect.fields(target))
Reflect.setField(ret, field, crawl(Reflect.field(target, field), transformer, retyper, pos));
if (Std.is(ret.expr, ExprDef)) {
ret = transformer(ret);
if (pos != null) ret.pos = pos;
}
ret;
}
static public inline function iterate(target:Expr, body:Expr, ?loopVar:String = 'i', ?pos:Position) static public inline function iterate(target:Expr, body:Expr, ?loopVar:String = 'i', ?pos:Position)
return macro @:pos(pos.sanitize()) for ($i{loopVar} in $target) $body; return macro @:pos(pos.sanitize()) for ($i{loopVar} in $target) $body;
static public function toFields(object:Dynamic<Expr>, ?pos:Position) static public function toFields(object:Dict<Expr>, ?pos:Position)
return EObjectDecl([for (field in Reflect.fields(object)) return EObjectDecl([for (field in object.keys())
{ field:field, expr: untyped Reflect.field(object, field) } { field:field, expr: object[field] }
]).at(pos); ]).at(pos);
static public inline function log(e:Expr, ?pos:PosInfos):Expr { static public inline function log(e:Expr, ?pos:PosInfos):Expr {

View File

@@ -33,6 +33,22 @@ class Functions {
expr: if (makeReturn) EReturn(e).at(e.pos) else e expr: if (makeReturn) EReturn(e).at(e.pos) else e
} }
} }
static public function mapSignature(f:Function, transform):Function
return {
ret: Types.map(f.ret, transform),
args: [for (a in f.args) {
name: a.name,
value: a.value,
type: Types.map(a.type, transform),
#if haxe4
meta: a.meta,
#end
}],
expr: f.expr,
params: Types.mapTypeParamDecls(f.params, transform),
}
static public function getArgIdents(f:Function):Array<Expr> { static public function getArgIdents(f:Function):Array<Expr> {
var ret = []; var ret = [];
for (arg in f.args) for (arg in f.args)

View File

@@ -70,6 +70,79 @@ class Types {
getDeclaredFields(t.superClass.t.get(), out, marker); getDeclaredFields(t.superClass.t.get(), out, marker);
} }
static public function mapTypeParams(a:Array<TypeParam>, f)
return
if (a == null) null;
else [for (p in a) switch p {
case TPType(t): TPType(t.map(f));
default: p;
}];
static public function mapTypeParamDecls(a:Array<TypeParamDecl>, f:ComplexType->ComplexType):Array<TypeParamDecl>
return
if (a == null) null;
else [for (p in a) {
name: p.name,
meta: p.meta,
params: mapTypeParamDecls(p.params, f),
constraints: switch p.constraints {
case null: null;
case a: [for (t in a) map(t, f)];
}
}];
static public function mapTypePath(p:TypePath, transform):TypePath
return {
name: p.name,
pack: p.pack,
sub: p.sub,
params: mapTypeParams(p.params, transform),
}
static public function map(ct:ComplexType, transform:ComplexType->ComplexType) {
if (ct == null)
return null;
inline function rec(ct)
return map(transform(ct), transform);
function mapFields(fields:Array<Field>):Array<Field>
return [for (f in fields) {
name: f.name,
pos: f.pos,
kind: switch f.kind {
case FVar(t, e): FVar(rec(t), e);
case FProp(get, set, t, e): FProp(get, set, rec(t), e);
case FFun(f): FFun(Functions.mapSignature(f, transform));
},
access: f.access,
meta: switch f.meta {
case null: null;
case a: [for (m in a) {
name: m.name,
pos: m.pos,
params: [for (e in m.params) e.mapTypes(transform)],
}];
},
doc: f.doc,
}];
return transform(switch ct {
case TParent(t): TParent(rec(t));
#if haxe4
case TNamed(n, t): TNamed(n, rec(t));
case TIntersection(tl): TIntersection([for (t in tl) rec(t)]);
#end
case TOptional(t): TOptional(rec(t));
case TAnonymous(fields):
TAnonymous(mapFields(fields));
case TPath(p):
TPath(mapTypePath(p, transform));
case TExtend(paths, fields):
TExtend([for (p in paths) mapTypePath(p, transform)], mapFields(fields));
case TFunction(args, ret):
TFunction([for (a in args) rec(a)], rec(ret));
});
}
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
@@ -84,12 +157,28 @@ class Types {
} }
var ret = fieldsCache.get(id); var ret = fieldsCache.get(id);
if (substituteParams && ret.isSuccess()) { if (substituteParams && ret.isSuccess()) {
var fields = Reflect.copy(ret.sure()); ret = Success([
for (field in ret.sure()) ({
for (field in fields) name: field.name,
field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params); type: haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params),
pos: field.pos,
meta: field.meta,
doc: field.doc,
expr: function () return field.expr(),
isPublic: field.isPublic,
params: field.params,
kind: field.kind,
overloads: field.overloads,
#if haxe4
isExtern: field.isExtern,
isFinal: field.isFinal,
#end
}:ClassField)
]);
} }
#if !haxe4
fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache
#end
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');