Compare commits

..

12 Commits

Author SHA1 Message Date
Juraj Kirchheim
8c50dcf04b Release 0.19.1 2020-04-29 20:58:00 +02:00
Juraj Kirchheim
4bc63c6eb0 Resolves #23. 2020-04-24 15:55:39 +02:00
Juraj Kirchheim
acb7237ae4 Resolves #12. Resolves #18. 2020-04-24 15:38:51 +02:00
Juraj Kirchheim
e3bac681ce Allow for typemaps that reduce everything except null. 2020-04-24 15:29:24 +02:00
Juraj Kirchheim
34516086c9 Update setup. 2020-04-24 14:42:55 +02:00
Kevin Leung
b1e7bf51cf Release 0.19.0 2020-03-11 14:01:21 +08:00
Juraj Kirchheim
d5186f2fa2 Merge pull request #29 from haxetink/no-reflection
No reflection
2020-01-09 07:50:25 +01:00
Juraj Kirchheim
8890b62b46 Now we have it, I think ... 2020-01-06 14:53:06 +01:00
Juraj Kirchheim
cce63becd6 Whoops. 2020-01-06 14:40:38 +01:00
Juraj Kirchheim
a101378a4c Try to be smarter about when bouncing initialization. 2020-01-06 14:35:26 +01:00
Juraj Kirchheim
b48b300cbc Add some missing cases in type mapping. 2020-01-04 15:45:29 +01:00
Juraj Kirchheim
27c9b20330 Get rid of all reflection. 2020-01-04 14:20:40 +01:00
15 changed files with 478 additions and 304 deletions

View File

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

View File

@@ -15,14 +15,14 @@ os:
env:
- HAXE_VERSION=3.4.7
- HAXE_VERSION=edge
install:
- npm i -g lix@15.5.4
- lix install haxe $HAXE_VERSION
- lix download
script:
- lix run travix node -lib hx3compat
- lix run travix node
jobs:
include:

View File

@@ -1,3 +1,3 @@
-D hx3compat=1.0.0
# @install: lix --silent download "haxelib:/hx3compat#1.0.0" into hx3compat/1.0.0/haxelib
-cp ${HAXE_LIBCACHE}/hx3compat/1.0.0/haxelib/std
# @install: lix --silent download "haxelib:/hx3compat#1.0.3" into hx3compat/1.0.3/haxelib
-cp ${HAXE_LIBCACHE}/hx3compat/1.0.3/haxelib/std
-D hx3compat=1.0.3

View File

@@ -1,6 +1,6 @@
# @install: lix --silent download "haxelib:hxnodejs#4.0.9" into hxnodejs/4.0.9/haxelib
-D hxnodejs=4.0.9
-cp ${HAXESHIM_LIBCACHE}/hxnodejs/4.0.9/haxelib/src
-D nodejs
# @install: lix --silent download "haxelib:/hxnodejs#12.0.0" into hxnodejs/12.0.0/haxelib
-cp ${HAXE_LIBCACHE}/hxnodejs/12.0.0/haxelib/src
-D hxnodejs=12.0.0
--macro allowPackage('sys')
--macro _hxnodejs.VersionWarning.include()
# should behave like other target defines and not be defined in macro context
--macro define('nodejs')

View File

@@ -1,20 +1,20 @@
{
"name": "tink_macro",
"license": "MIT",
"description": "The macro toolkit ;)",
"classPath": "src",
"dependencies": {
"tink_core": ""
},
"url": "https://github.com/haxetink/tink_macro",
"contributors": [
"back2dos"
],
"version": "0.19.1",
"releasenote": "More fine grained control over type normalization in BuildCache.",
"tags": [
"tink",
"macro",
"utility"
],
"classPath": "src",
"description": "The macro toolkit ;)",
"contributors": [
"back2dos"
],
"releasenote": "Compat with haxe 4 rc4 onward.",
"version": "0.18.0",
"url": "http://haxetink.org/tink_macro",
"dependencies": {
"tink_core": ""
}
"license": "MIT"
}

View File

@@ -31,23 +31,23 @@ typedef BuildContext3 = {>BuildContext2,
type3:Type,
}
class BuildCache {
class BuildCache {
@:persistent static var cache = new Map();
static public function getType3(name, ?types, ?pos:Position, build:BuildContext3->TypeDefinition) {
static public function getType3(name, ?types, ?pos:Position, build:BuildContext3->TypeDefinition, ?normalizer:Type->Type) {
if (types == null)
switch Context.getLocalType() {
case TInst(_.toString() == name => true, [t1, t2, t3]):
types = { t1: t1, t2: t2, t3: t3 };
default:
throw 'assert';
}
}
var t1 = types.t1.toComplexType(),
t2 = types.t2.toComplexType(),
t3 = types.t2.toComplexType();
return getType(name, (macro : { t1: $t1, t2: $t2, t3: $t3 } ).toType(), pos, function (ctx) return build({
type: types.t1,
type2: types.t2,
@@ -55,62 +55,62 @@ class BuildCache {
pos: ctx.pos,
name: ctx.name,
usings: ctx.usings
}));
}), normalizer);
}
static public function getTypeN(name, ?types, ?pos:Position, build:BuildContextN->TypeDefinition) {
static public function getTypeN(name, ?types, ?pos:Position, build:BuildContextN->TypeDefinition, ?normalizer:Type->Type) {
if (pos == null)
pos = Context.currentPos();
if (types == null)
switch Context.getLocalType() {
case TInst(_.toString() == name => true, params):
types = params;
default:
throw 'assert';
}
}
var compound = ComplexType.TAnonymous([for (i in 0...types.length) {
name: 't$i',
pos: pos,
kind: FVar(switch types[i] {
case TInst(_.get().kind => KExpr(e), _):
case TInst(_.get().kind => KExpr(e), _):
TPath('tink.macro.ConstParam'.asTypePath([TPExpr(e)]));
case t: t.toComplex();
}),
}]).toType();
return getType(name, compound, pos, function (ctx) return build({
types: types,
pos: ctx.pos,
name: ctx.name,
usings: ctx.usings
}));
}
static public function getType2(name, ?types, ?pos:Position, build:BuildContext2->TypeDefinition) {
}), normalizer);
}
static public function getType2(name, ?types, ?pos:Position, build:BuildContext2->TypeDefinition, ?normalizer:Type->Type) {
if (types == null)
switch Context.getLocalType() {
case TInst(_.toString() == name => true, [t1, t2]):
types = { t1: t1, t2: t2 };
default:
throw 'assert';
}
}
var t1 = types.t1.toComplexType(),
t2 = types.t2.toComplexType();
return getType(name, (macro : { t1: $t1, t2: $t2 } ).toType(), pos, function (ctx) return build({
type: types.t1,
type2: types.t2,
pos: ctx.pos,
name: ctx.name,
usings: ctx.usings
}));
}), normalizer);
}
static public function getParams(name:String, ?pos:Position)
static public function getParams(name:String, ?pos:Position)
return
switch Context.getLocalType() {
case TInst(_.toString() == name => true, v):
@@ -119,9 +119,9 @@ class BuildCache {
pos.makeFailure('Expected $name');
case v:
pos.makeFailure('$v should be a class');
}
}
static public function getParam(name:String, ?pos:Position)
static public function getParam(name:String, ?pos:Position)
return
getParams(name, pos)
.flatMap(function (args:Array<Type>) return switch args {
@@ -130,18 +130,18 @@ class BuildCache {
default: pos.makeFailure('too many parameters');
});
static public function getType(name, ?type, ?pos:Position, build:BuildContext->TypeDefinition) {
static public function getType(name, ?type, ?pos:Position, build:BuildContext->TypeDefinition, ?normalizer:Type->Type) {
if (type == null)
type = getParam(name, pos).sure();
var forName =
var forName =
switch cache[name] {
case null: cache[name] = new Group(name);
case null: cache[name] = new Group(name, normalizer);
case v: v;
}
var ret = forName.get(type, pos.sanitize(), build);
var ret = forName.get(type, pos.sanitize(), build);
ret.getFields();// workaround for https://github.com/HaxeFoundation/haxe/issues/7905
return ret;
}
@@ -152,23 +152,24 @@ private typedef Entry = {
}
private class Group {
var name:String;
var counter = 0;
var entries = new TypeMap<Entry>();
public function new(name) {
var entries:TypeMap<Entry>;
public function new(name, ?normalizer) {
this.name = name;
this.entries = new TypeMap(normalizer);
}
public function get(type:Type, pos:Position, build:BuildContext->TypeDefinition):Type {
function make(path:String) {
var usings = [];
var def = build({
pos: pos,
type: type,
usings: usings,
pos: pos,
type: type,
usings: usings,
name: path.split('.').pop()
});
@@ -177,15 +178,15 @@ private class Group {
return Context.getType(path);
}
function doMake()
while (true)
function doMake()
while (true)
switch '$name${counter++}' {
case _.definedType() => Some(_):
case v:
return make(v);
}
}
return
return
switch entries.get(type) {
case null:
doMake();

View File

@@ -21,7 +21,7 @@ class Constructor {
var afterArgs:Array<FunctionArg>;
var pos:Position;
var onGenerateHooks:Array<Function->Void>;
var superCall:Array<Expr>;
var superCall:Expr;
var owner:ClassBuilder;
var meta:Metadata;
public var isPublic:Null<Bool>;
@@ -57,17 +57,12 @@ class Constructor {
default: oldStatements = [f.expr];
}
}
for (i in 0...oldStatements.length)
switch oldStatements[i] {
case macro super($a{_}):
superCall = oldStatements.splice(0, i + 1);
break;
default:
superCall =
if (oldStatements.length == 0) [].toBlock();
else switch oldStatements[0] {
case macro super($a{_}): oldStatements.shift();
default: [].toBlock();
}
if (superCall == null)
superCall = [];
}
public function getArgList():Array<FunctionArg>
@@ -100,10 +95,33 @@ class Constructor {
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');
@@ -135,8 +153,14 @@ class Constructor {
macro @:pos(pos) this.$name = $e;
case FVar(AccNo | AccNormal, AccNever):
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):
setDirectly(fields['get_$name'].expr());
#end
case FVar(_, AccCall):
setDirectly(fields['set_$name'].expr());
default:
@@ -146,14 +170,17 @@ class Constructor {
}).bounce(), options.prepend);
}
else
addStatement(macro @:pos(pos) this.$name = $e, options.prepend);
addStatement(
initStatement,
options.prepend
);
}
public inline function publish()
if (isPublic == null)
isPublic = true;
function toBlock()
return superCall
return [superCall]
.concat(nuStatements)
.concat(oldStatements)
.toBlock(pos);

View File

@@ -1,12 +1,11 @@
package tink.macro;
import Type in Inspect;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.PosInfos;
import haxe.macro.Printer;
import haxe.DynamicAccess as Dict;
using Lambda;
using StringTools;
@@ -20,6 +19,7 @@ typedef VarDecl = { name : String, type : ComplexType, expr : Null<Expr> };
typedef ParamSubst = {
var exists(default, null):String->Bool;
var get(default, null):String->ComplexType;
function iterator():Iterator<ComplexType>;
}
private class Heureka { public function new() {} }
@@ -34,7 +34,7 @@ class Exprs {
case _ if (condition(e)): throw new Heureka();
default: haxe.macro.ExprTools.iter(e, seek);
}
return try {
haxe.macro.ExprTools.iter(e, seek);
false;
@@ -48,22 +48,23 @@ class Exprs {
static public inline function as(e:Expr, c:ComplexType)
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)
nuPos = Context.currentPos();
if (rules == null)
rules = { };
function replace(s:String)
return {
if (Reflect.hasField(rules, s))
Reflect.field(rules, s)
else if (s.startsWith('tmp')) {
Reflect.setField(rules, s, MacroApi.tempName(s.substr(3)));
replace(s);
}
else s;
function replace(s:String):String
return switch rules[s] {
case null:
if (s.startsWith('tmp')) {
rules[s] = MacroApi.tempName(s.substr(3));
replace(s);
}
else s;
case v: v;
}
return e.transform(function (e:Expr) {
return
if (Context.getPosInfos(e.pos).file != callPos.fileName) e;
@@ -71,7 +72,7 @@ class Exprs {
e.pos = nuPos;
switch (e.expr) {
case EVars(vars):
for (v in vars)
for (v in vars)
v.name = replace(v.name);
e;
case EField(owner, field):
@@ -86,7 +87,7 @@ class Exprs {
for (f in fields)
f.field = replace(f.field);
e;
default:
default:
switch (e.getIdent()) {
case Success(s): replace(s).resolve(e.pos);
default: e;
@@ -95,10 +96,10 @@ class Exprs {
}
});
}
static public function withPrivateAccess(e:Expr)
return
e.transform(function (e:Expr)
return
e.transform(function (e:Expr)
return
switch (e.expr) {
case EField(owner, field):
@@ -106,72 +107,95 @@ class Exprs {
default: e;
}
);
static public function getPrivate(e:Expr, field:String, ?pos:Position)
return macro @:pos(pos.sanitize()) @:privateAccess $e.$field;
static public function substitute(source:Expr, vars:Dynamic<Expr>, ?pos)
return
static public function substitute(source:Expr, vars:Dict<Expr>, ?pos)
return
transform(source, function (e:Expr) {
return
switch (e.getIdent()) {
case Success(name):
if (Reflect.hasField(vars, name))
Reflect.field(vars, name);
else
e;
default: e;
}
return switch e {
case macro $i{name}:
switch vars[name] {
case null: e;
case v: v;
}
default: e;
}
}, pos);
static public inline function ifNull(e:Expr, fallback:Expr)
static public inline function ifNull(e:Expr, fallback:Expr)
return
switch e {
case macro null: fallback;
default: e;
}
static public function substParams(source:Expr, subst:ParamSubst, ?pos):Expr
return crawl(
source,
function (e)
return switch e.expr {
case ENew({ pack: [], name: name }, args) if (subst.exists(name)):
switch subst.get(name) {
case TPath(p):
ENew(p, args).at(e.pos);
default: e;//TODO: report an error?
}
case EConst(CIdent(name)) if (subst.exists(name)):
static public function substParams(source:Expr, subst:ParamSubst, ?pos:Position):Expr {
if (!subst.iterator().hasNext())
return source;
function replace(ct:ComplexType)
return switch ct {
case TPath({ pack: [], name: name }) if (subst.exists(name)):
subst.get(name);
default: ct;
}
return source
.transform(
function (e) return switch e {
case macro $i{name} if (subst.exists(name)):
switch subst.get(name) {
case TPath({ pack: pack, name: name }):
pack.concat([name]).drill(e.pos);
default: e;//TODO: report an error?
}
default: e;
},
function (c:ComplexType)
return
switch (c) {
case TPath({ pack: [], name: name }) if (subst.exists(name)):
subst.get(name);
default: c;
})
.mapTypes(replace);
}
static public function mapTypes(e:Expr, transformer:ComplexType->ComplexType, ?pos:Position) {
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}');
}
, pos);
static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr
return crawl(source, transformer, function (t) return t, pos);
static function crawlArray(a:Array<Dynamic>, transformer:Expr->Expr, retyper:ComplexType-> ComplexType, pos:Position):Array<Dynamic>
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 public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr {
function apply(e:Expr)
return switch e {
case null | { expr: null }: e;
default: transformer(haxe.macro.ExprTools.map(e, apply));
}
return apply(source);
}
static public function getIterType(target:Expr)
return
if (a == null) a;
else
[for (v in a)
crawl(v, transformer, retyper, pos)
];
static public function getIterType(target:Expr)
return
(macro @:pos(target.pos) {
var t = null,
target = $target;
@@ -179,21 +203,21 @@ class Exprs {
t = i;
t;
}).typeof();
static public function yield(e:Expr, yielder:Expr->Expr, ?options: { ?leaveLoops: Bool }):Expr {
inline function rec(e)
inline function rec(e)
return yield(e, yielder, options);
if (options == null)
options = { };
var loops = options.leaveLoops != true;
return
if (e == null || e.expr == null) e;
else switch (e.expr) {
case EVars(_):
e.pos.error('Variable declaration not supported here');
case EBlock(exprs) if (exprs.length > 0):
case EBlock(exprs) if (exprs.length > 0):
exprs = exprs.copy();
exprs.push(rec(exprs.pop()));
EBlock(exprs).at(e.pos);
@@ -201,10 +225,11 @@ class Exprs {
,ETernary(econd, eif, eelse):
EIf(econd, rec(eif), rec(eelse)).at(e.pos);
case ESwitch(e, cases, edef):
cases = Reflect.copy(cases);//not exactly pretty, but does the job
for (c in cases)
c.expr = rec(c.expr);
ESwitch(e, cases, rec(edef)).at(e.pos);
ESwitch(e, [for (c in cases) {
expr: rec(c.expr),
guard: c.guard,
values: c.values,
}], rec(edef)).at(e.pos);
case EFor(it, expr) if (loops):
EFor(it, rec(expr)).at(e.pos);
case EWhile(cond, body, normal) if (loops):
@@ -218,101 +243,77 @@ class Exprs {
default: yielder(e);
}
}
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;
static public function toFields(object:Dynamic<Expr>, ?pos:Position)
return EObjectDecl([for (field in Reflect.fields(object))
{ field:field, expr: untyped Reflect.field(object, field) }
static public function toFields(object:Dict<Expr>, ?pos:Position)
return EObjectDecl([for (field in object.keys())
{ field:field, expr: object[field] }
]).at(pos);
static public inline function log(e:Expr, ?pos:PosInfos):Expr {
haxe.Log.trace(e.toString(), pos);
return e;
}
static public inline function reject(e:Expr, ?reason:String = 'cannot handle expression'):Dynamic
static public inline function reject(e:Expr, ?reason:String = 'cannot handle expression'):Dynamic
return e.pos.error(reason);
static public inline function toString(e:Expr):String
static public inline function toString(e:Expr):String
return new haxe.macro.Printer().printExpr(e);
static public inline function at(e:ExprDef, ?pos:Position)
static public inline function at(e:ExprDef, ?pos:Position)
return {
expr: e,
pos: pos.sanitize()
};
static public inline function instantiate(s:String, ?args:Array<Expr>, ?params:Array<TypeParam>, ?pos:Position)
static public inline function instantiate(s:String, ?args:Array<Expr>, ?params:Array<TypeParam>, ?pos:Position)
return s.asTypePath(params).instantiate(args, pos);
static public inline function assign(target:Expr, value:Expr, ?op:Binop, ?pos:Position)
static public inline function assign(target:Expr, value:Expr, ?op:Binop, ?pos:Position)
return binOp(target, value, op == null ? OpAssign : OpAssignOp(op), pos);
static public inline function define(name:String, ?init:Expr, ?typ:ComplexType, ?pos:Position)
static public inline function define(name:String, ?init:Expr, ?typ:ComplexType, ?pos:Position)
return at(EVars([ { name:name, type: typ, expr: init } ]), pos);
static public inline function add(e1:Expr, e2, ?pos)
static public inline function add(e1:Expr, e2, ?pos)
return binOp(e1, e2, OpAdd, pos);
static public inline function unOp(e:Expr, op, ?postFix = false, ?pos)
static public inline function unOp(e:Expr, op, ?postFix = false, ?pos)
return EUnop(op, postFix, e).at(pos);
static public inline function binOp(e1:Expr, e2, op, ?pos)
static public inline function binOp(e1:Expr, e2, op, ?pos)
return EBinop(op, e1, e2).at(pos);
static public inline function field(e:Expr, field, ?pos)
static public inline function field(e:Expr, field, ?pos)
return EField(e, field).at(pos);
static public inline function call(e:Expr, ?params, ?pos)
static public inline function call(e:Expr, ?params, ?pos)
return ECall(e, params == null ? [] : params).at(pos);
static public inline function toExpr(v:Dynamic, ?pos:Position)
static public inline function toExpr(v:Dynamic, ?pos:Position)
return Context.makeExpr(v, pos.sanitize());
static public inline function toArray(exprs:Iterable<Expr>, ?pos)
static public inline function toArray(exprs:Iterable<Expr>, ?pos)
return EArrayDecl(exprs.array()).at(pos);
static public inline function toMBlock(exprs:Array<Expr>, ?pos)
static public inline function toMBlock(exprs:Array<Expr>, ?pos)
return EBlock(exprs).at(pos);
static public inline function toBlock(exprs:Iterable<Expr>, ?pos)
return toMBlock(Lambda.array(exprs), pos);
static public inline function toBlock(exprs:Iterable<Expr>, ?pos)
return toMBlock(Lambda.array(exprs), pos);
static public function drill(parts:Array<String>, ?pos:Position, ?target:Expr) {
if (target == null)
if (target == null)
target = at(EConst(CIdent(parts.shift())), pos);
for (part in parts)
target = field(target, part, pos);
return target;
return target;
}
static public inline function resolve(s:String, ?pos)
static public inline function resolve(s:String, ?pos)
return drill(s.split('.'), pos);
static var scopes = new Array<Array<Var>>();
@@ -320,7 +321,7 @@ class Exprs {
static function inScope<T>(a:Array<Var>, f:Void->T) {
scopes.push(a);
inline function leave()
inline function leave()
scopes.pop();
try {
var ret = f();
@@ -330,24 +331,24 @@ class Exprs {
catch (e:Dynamic) {
leave();
return Error.rethrow(e);
}
}
}
static public function scoped<T>(f:Void->T)
static public function scoped<T>(f:Void->T)
return inScope([], f);
static public function inSubScope<T>(f:Void->T, a:Array<Var>)
static public function inSubScope<T>(f:Void->T, a:Array<Var>)
return inScope(switch scopes[scopes.length - 1] {
case null: a;
case v: v.concat(a);
}, f);
static public function typeof(expr:Expr, ?locals)
return
try {
if (locals == null)
locals = scopes[scopes.length - 1];
if (locals != null)
if (locals != null)
expr = [EVars(locals).at(expr.pos), expr].toMBlock(expr.pos);
Success(Context.typeof(expr));
}
@@ -356,20 +357,20 @@ class Exprs {
}
catch (e:Dynamic) {
expr.pos.makeFailure(e);
}
static public inline function cond(cond:Expr, cons:Expr, ?alt:Expr, ?pos)
}
static public inline function cond(cond:Expr, cons:Expr, ?alt:Expr, ?pos)
return EIf(cond, cons, alt).at(pos);
static public function isWildcard(e:Expr)
return
static public function isWildcard(e:Expr)
return
switch e {
case macro _: true;
default: false;
}
static public function getString(e:Expr)
return
static public function getString(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
@@ -377,10 +378,10 @@ class Exprs {
default: e.pos.makeFailure(NOT_A_STRING);
}
default: e.pos.makeFailure(NOT_A_STRING);
}
static public function getInt(e:Expr)
return
}
static public function getInt(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
@@ -388,22 +389,22 @@ class Exprs {
default: e.pos.makeFailure(NOT_AN_INT);
}
default: e.pos.makeFailure(NOT_AN_INT);
}
static public function getIdent(e:Expr)
return
}
static public function getIdent(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
case CIdent(id): Success(id);
default: e.pos.makeFailure(NOT_AN_IDENT);
}
default:
default:
e.pos.makeFailure(NOT_AN_IDENT);
}
static public function getName(e:Expr)
return
}
static public function getName(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
@@ -411,15 +412,15 @@ class Exprs {
default: e.pos.makeFailure(NOT_A_NAME);
}
default: e.pos.makeFailure(NOT_A_NAME);
}
static public function getFunction(e:Expr)
}
static public function getFunction(e:Expr)
return
switch (e.expr) {
case EFunction(_, f): Success(f);
default: e.pos.makeFailure(NOT_A_FUNCTION);
}
static public function concat(e:Expr, with:Expr, ?pos) {
if(pos == null) pos = e.pos;
return
@@ -430,7 +431,7 @@ class Exprs {
default: EBlock([e, with]).at(pos);
}
}
static var FIRST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
static var LATER = FIRST + '0123456789';
@@ -438,7 +439,7 @@ class Exprs {
var ret = FIRST.charAt(i % FIRST.length);
i = Std.int(i / FIRST.length);
while (i > 0) {
ret += LATER.charAt(i % LATER.length);
i = Std.int(i / LATER.length);
@@ -446,7 +447,7 @@ class Exprs {
return ret;
}
static inline var NOT_AN_INT = "integer constant expected";
static inline var NOT_AN_IDENT = "identifier expected";
static inline var NOT_A_STRING = "string constant expected";

View File

@@ -14,9 +14,9 @@ private typedef Kind = String;
#end
class Functions {
static public inline function asExpr(f:Function, ?kind:Kind, ?pos)
static public inline function asExpr(f:Function, ?kind:Kind, ?pos)
return EFunction(kind, f).at(pos);
static public inline function toArg(name:String, ?t, ?opt = false, ?value = null):FunctionArg {
return {
name: name,
@@ -31,8 +31,24 @@ class Functions {
ret: ret,
params: params == null ? [] : params,
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> {
var ret = [];
for (arg in f.args)

View File

@@ -8,14 +8,33 @@ import haxe.macro.Type;
using tink.MacroApi;
class TypeMap<V> extends BalancedTree<Type, V> implements IMap<Type, V> {
var follow:Bool;
public function new(?noFollow:Bool) {
this.follow = noFollow != true;
var normalize:Type->Type;
public function new(?normalize:Type->Type) {
this.normalize = switch normalize {
case null: function (t) return t.reduce();
case fn: fn;
}
super();
}
override function compare(k1:Type, k2:Type):Int
return k1.compare(k2, follow);
return normalize(k1).compare(normalize(k2), false);
static public function noFollow(t:Type)
return t;
static public function keepNull(t:Type):Type
return switch t {
case TAbstract(_.get() => { name: 'Null', pack: [] }, [t])
#if !haxe4 | TType(_.get() => { name: 'Null', pack: []}, [t]) #end
:
var ct = keepNull(t).toComplex({ direct: true });
(macro : Null<$ct>).toType().sure();
case TLazy(f): keepNull(f());
case TType(_, _): keepNull(Context.follow(t, true));
default: t;
}
}

View File

@@ -70,6 +70,79 @@ class Types {
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 public function getFields(t:Type, ?substituteParams = true)
return
@@ -84,12 +157,28 @@ class Types {
}
var ret = fieldsCache.get(id);
if (substituteParams && ret.isSuccess()) {
var fields = Reflect.copy(ret.sure());
for (field in fields)
field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params);
ret = Success([
for (field in ret.sure()) ({
name: field.name,
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
#end
ret;
case TAnonymous(anon): Success(anon.get().fields);
default: Context.currentPos().makeFailure('type $t has no fields');
@@ -101,8 +190,8 @@ class Types {
case TInst(t, _): Success(t.get().statics.get());
default: Failure('type has no statics');
}
static public function getPosition(t:Type)
return
switch t {
@@ -115,8 +204,8 @@ class Types {
case TDynamic(v) if(v != null): getPosition(v);
default: Failure('type "$t" has no position');
}
static public function toString(t:ComplexType)
return new Printer().printComplexType(t);
@@ -202,7 +291,7 @@ class Types {
}
static public function intersect(types:Array<ComplexType>, ?pos:Position):Outcome<ComplexType, Error> {
if (types.length == 1) return Success(types[1]);
var paths = [],
@@ -211,17 +300,17 @@ class Types {
for (t in types)
switch t {
case TPath(p): paths.push(p);
case TAnonymous(f):
case TAnonymous(f):
for (f in f) fields.push(f);
case TExtend(p, f):
case TExtend(p, f):
for (f in f) fields.push(f);
for (p in p) paths.push(p);
default:
return Failure(new Error(t.toString() + ' cannot be interesected', pos));
}
@@ -251,23 +340,23 @@ class Types {
t1 = t1.reduce();
t2 = t2.reduce();
}
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)
case v: v;
}
}
}
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() {
case Failure(SUGGESTIONS.match(_.message) => true): SUGGESTIONS.matched(0);
default: '';
}
static public function toDecl(p:TypeParameter):TypeParamDecl
static public function toDecl(p:TypeParameter):TypeParamDecl
return {
name: p.name,
constraints: switch p.t {
@@ -279,4 +368,4 @@ class Types {
static public function toTypeParam(p:TypeParameter):TypeParam
return TPType(p.t.toComplex());
}
}

View File

@@ -1,2 +1,3 @@
-cp tests
-lib hx3compat
-main Run

1
tests/MyString.hx Normal file
View File

@@ -0,0 +1 @@
typedef MyString = String;

View File

@@ -3,8 +3,8 @@ package ;
import haxe.unit.*;
class Run {
#if !macro
static function main()
#if !macro
static function main()
test();//It compiles ...
#else
static var cases:Array<TestCase> = [
@@ -19,12 +19,13 @@ class Run {
macro static function test() {
var runner = new TestRunner();
tink.macro.ClassBuilder;
tink.macro.BuildCache;
for (c in cases)
runner.add(c);
runner.run();
if (!runner.result.success)
haxe.macro.Context.error(runner.result.toString(), haxe.macro.Context.currentPos());
return macro {
trace('Let\'s ship it!');
}

View File

@@ -1,17 +1,17 @@
package;
import haxe.unit.TestCase;
import haxe.macro.Expr;
import tink.macro.TypeMap;
using haxe.macro.Context;
using tink.MacroApi;
class TypeMapTest extends TestCase {
function testMap() {
var t = new TypeMap();
var t1 = (macro [{ foo: [{ bar: '5' }]}]).typeof();
var t2 = (macro [{ foo: [{ bar: 5 }]}]).typeof();
var t1 = (macro [{ foo: [{ bar: '5' }]}]).typeof().sure();
var t2 = (macro [{ foo: [{ bar: 5 }]}]).typeof().sure();
t.set(t1, 0);
assertEquals(Lambda.count(t), 1);
t.set(t2, 1);
@@ -20,11 +20,29 @@ class TypeMapTest extends TestCase {
assertEquals(Lambda.count(t), 2);
t.set(t2, 3);
assertEquals(Lambda.count(t), 2);
assertEquals(t.get(t1), 2);
assertEquals(t.get(t2), 3);
assertTrue(true);
}
function testNormalization() {
for (settings in [
{ count: 4, map: new TypeMap(TypeMap.noFollow)},
{ count: 2, map: new TypeMap(TypeMap.keepNull)},
{ count: 1, map: new TypeMap()},
]) {
function set(ct:ComplexType)
settings.map.set(ct.toType().sure(), ct);
set(macro : String);
set(macro : MyString);
set(macro : Null<String>);
set(macro : Null<MyString>);
assertEquals(settings.count, Lambda.count(settings.map));
}
}
}