530 lines
15 KiB
Haxe
530 lines
15 KiB
Haxe
package hscript.macros;
|
|
|
|
/*
|
|
* Copyright (C)2005-2019 Haxe Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
|
|
import haxe.macro.Context;
|
|
import haxe.macro.Expr;
|
|
import haxe.macro.Type;
|
|
import Type as StdType;
|
|
|
|
using Lambda;
|
|
|
|
/**
|
|
This class provides some utility methods to work with types. It is
|
|
best used through 'using haxe.macro.TypeTools' syntax and then provides
|
|
additional methods on haxe.macro.Type instances.
|
|
**/
|
|
#if hl
|
|
@:hlNative("macro")
|
|
#end
|
|
class FixedTypeTools {
|
|
static function nullable(complexType:ComplexType):ComplexType
|
|
return macro:Null<$complexType>;
|
|
|
|
public static function toField(cf:ClassField):Field {
|
|
function varAccessToString(va:VarAccess, getOrSet:String):String {
|
|
return {
|
|
switch (va) {
|
|
case AccNormal | AccCtor: "default";
|
|
case AccNo: "null";
|
|
case AccNever: "never";
|
|
case AccResolve: throw "Invalid " + StdType.enumConstructor(cf.type) + " in varAccessToString";
|
|
case AccCall: getOrSet;
|
|
case AccInline: "default";
|
|
case AccRequire(_, _): "default";
|
|
}
|
|
}
|
|
}
|
|
|
|
var access = cf.isPublic ? [APublic] : [APrivate];
|
|
if (cf.meta.has(":final")) {
|
|
access.push(AFinal);
|
|
}
|
|
|
|
if (cf.params.length != 0)
|
|
throw "Invalid " + StdType.enumConstructor(cf.type) + " has more than 0 params";
|
|
|
|
return {
|
|
name: cf.name,
|
|
doc: cf.doc,
|
|
access: access,
|
|
kind: switch ([cf.kind, cf.type]) {
|
|
case [FVar(read, write), ret]:
|
|
FProp(varAccessToString(read, "get"), varAccessToString(write, "set"), toComplexType(ret), null);
|
|
case [FMethod(_), TFun(args, ret)]:
|
|
Sys.println("Converting " + cf.name);
|
|
FFun({
|
|
args: [
|
|
for (a in args)
|
|
{
|
|
name: a.name,
|
|
opt: a.opt,
|
|
type: toComplexType(a.t),
|
|
}
|
|
],
|
|
ret: toComplexType(ret),
|
|
expr: null,
|
|
});
|
|
case [FMethod(_), TLazy(f)]:
|
|
Sys.println("Converting lazy " + cf.name + " in " + cf.pos);
|
|
switch(f()) {
|
|
case TFun(args, ret):
|
|
FFun({
|
|
args: [
|
|
for (a in args)
|
|
{
|
|
name: a.name,
|
|
opt: a.opt,
|
|
type: toComplexType(a.t),
|
|
}
|
|
],
|
|
ret: toComplexType(ret),
|
|
expr: null,
|
|
});
|
|
default:
|
|
throw "Invalid " + StdType.enumConstructor(cf.type) + " when converting to Field , " + cf.kind + ", " + cf.type;
|
|
}
|
|
default:
|
|
throw "Invalid " + StdType.enumConstructor(cf.type) + " when converting to Field , " + cf.kind + ", " + cf.type;
|
|
},
|
|
pos: cf.pos,
|
|
meta: cf.meta.get(),
|
|
}
|
|
}
|
|
|
|
public static function getAccess(cf:ClassField):Array<Access> {
|
|
var access = cf.isPublic ? [APublic] : [APrivate];
|
|
if (cf.meta.has(":final") || cf.isFinal) {
|
|
access.push(AFinal);
|
|
}
|
|
switch ([cf.kind, cf.type]) {
|
|
case [FMethod(kind), TFun(_, _)] | [FMethod(kind), TLazy(_)]:
|
|
if(kind == MethInline)
|
|
access.push(AInline);
|
|
if(kind == MethDynamic)
|
|
access.push(ADynamic);
|
|
default:
|
|
}
|
|
return access;
|
|
}
|
|
|
|
public static function toSimpleField(cf:ClassField):Field {
|
|
function varAccessToString(va:VarAccess, getOrSet:String):String {
|
|
return {
|
|
switch (va) {
|
|
case AccNormal | AccCtor: "default";
|
|
case AccNo: "null";
|
|
case AccNever: "never";
|
|
case AccResolve: throw "Invalid " + StdType.enumConstructor(cf.type) + " in varAccessToString";
|
|
case AccCall: getOrSet;
|
|
case AccInline: "default";
|
|
case AccRequire(_, _): "default";
|
|
}
|
|
}
|
|
}
|
|
|
|
var access = cf.isPublic ? [APublic] : [APrivate];
|
|
if (cf.meta.has(":final")) {
|
|
access.push(AFinal);
|
|
}
|
|
|
|
if (cf.params.length != 0)
|
|
throw "Invalid " + StdType.enumConstructor(cf.type) + " has more than 0 params";
|
|
|
|
return {
|
|
name: cf.name,
|
|
doc: cf.doc,
|
|
access: access,
|
|
kind: switch ([cf.kind, cf.type]) {
|
|
case [FVar(read, write), ret]:
|
|
FProp(varAccessToString(read, "get"), varAccessToString(write, "set"), toComplexType(ret), null);
|
|
case [FMethod(_), TFun(args, ret)]:
|
|
Sys.println("Converting " + cf.name);
|
|
FFun({
|
|
args: [
|
|
for (a in args)
|
|
{
|
|
name: a.name,
|
|
opt: a.opt,
|
|
type: null,//toComplexType(a.t),
|
|
}
|
|
],
|
|
ret: null,//toComplexType(ret),
|
|
expr: null,
|
|
});
|
|
case [FMethod(_), TLazy(f)]:
|
|
Sys.println("Converting lazy " + cf.name + " in " + cf.pos);
|
|
switch(f()) {
|
|
case TFun(args, ret):
|
|
FFun({
|
|
args: [
|
|
for (a in args)
|
|
{
|
|
name: a.name,
|
|
opt: a.opt,
|
|
type: null,//toComplexType(a.t),
|
|
}
|
|
],
|
|
ret: null,//toComplexType(ret),
|
|
expr: null,
|
|
});
|
|
default:
|
|
throw "Invalid " + StdType.enumConstructor(cf.type) + " when converting to Field , " + cf.kind + ", " + cf.type;
|
|
}
|
|
default:
|
|
throw "Invalid " + StdType.enumConstructor(cf.type) + " when converting to Field , " + cf.kind + ", " + cf.type;
|
|
},
|
|
pos: cf.pos,
|
|
meta: cf.meta.get(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns a syntax-level type corresponding to Type `t`.
|
|
|
|
This function is mostly inverse to `ComplexTypeTools.toType`, but may
|
|
lose some information on types that do not have a corresponding syntax
|
|
version, such as monomorphs. In these cases, the result is null.
|
|
|
|
If `t` is null, an internal exception is thrown.
|
|
**/
|
|
public static function toComplexType(type:Null<Type>):Null<ComplexType>
|
|
return {
|
|
#if macro
|
|
Context.toComplexType(type);
|
|
#else
|
|
switch (type) {
|
|
case null:
|
|
null;
|
|
case TMono(_.get() => t):
|
|
t == null ? null : toComplexType(t);
|
|
case TEnum(_.get() => baseType, params):
|
|
TPath(toTypePath(baseType, params));
|
|
case TInst(_.get() => classType, params):
|
|
switch (classType.kind) {
|
|
case KTypeParameter(_):
|
|
TPath({
|
|
name: classType.name,
|
|
pack: [],
|
|
});
|
|
default:
|
|
TPath(toTypePath(classType, params));
|
|
}
|
|
case TType(_.get() => baseType, params):
|
|
TPath(toTypePath(baseType, params));
|
|
case TFun(args, ret):
|
|
TFunction([for (a in args) a.opt ? nullable(toComplexType(a.t)) : toComplexType(a.t)], toComplexType(ret));
|
|
case TAnonymous(_.get() => {fields: fields}):
|
|
TAnonymous([for (cf in fields) toField(cf)]);
|
|
case TDynamic(t):
|
|
if (t == null) {
|
|
macro:Dynamic;
|
|
} else {
|
|
var ct = toComplexType(t);
|
|
macro:Dynamic<$ct>;
|
|
}
|
|
case TLazy(f):
|
|
toComplexType(f());
|
|
case TAbstract(_.get() => baseType, params):
|
|
TPath(toTypePath(baseType, params));
|
|
default:
|
|
throw "Invalid type";
|
|
}
|
|
#end
|
|
}
|
|
|
|
static function toTypeParam(type:Type):TypeParam
|
|
return {
|
|
switch (type) {
|
|
case TInst(_.get() => {kind: KExpr(e)}, _): TPExpr(e);
|
|
case _: TPType(toComplexType(type));
|
|
}
|
|
}
|
|
|
|
static function toTypePath(baseType:BaseType, params:Array<Type>):TypePath
|
|
return {
|
|
var module = baseType.module;
|
|
{
|
|
pack: baseType.pack,
|
|
name: module.substring(module.lastIndexOf(".") + 1),
|
|
sub: baseType.name,
|
|
params: [for (t in params) toTypeParam(t)],
|
|
}
|
|
}
|
|
|
|
#if macro
|
|
/**
|
|
Follows all typedefs of `t` to reach the actual type.
|
|
|
|
If `once` is true, this function does not call itself recursively,
|
|
otherwise it does. This can be useful in cases where intermediate
|
|
typedefs might be of interest.
|
|
|
|
Affected types are monomorphs `TMono` and typedefs `TType(t,pl)`.
|
|
|
|
If `t` is null, an internal exception is thrown.
|
|
|
|
Usage example with monomorphs:
|
|
var t = Context.typeof(macro null); // TMono(<mono>)
|
|
var ts = Context.typeof(macro "foo"); //TInst(String,[])
|
|
Context.unify(t, ts);
|
|
trace(t); // TMono(<mono>)
|
|
trace(t.follow()); //TInst(String,[])
|
|
|
|
Usage example with typedefs:
|
|
var t = Context.typeof(macro ("foo" :MyString)); // typedef MyString = String
|
|
trace(t); // TType(MyString,[])
|
|
trace(t.follow()); //TInst(String,[])
|
|
**/
|
|
static public inline function follow(t:Type, ?once:Bool):Type
|
|
return Context.follow(t, once);
|
|
|
|
/**
|
|
Like `follow`, follows all typedefs of `t` to reach the actual type.
|
|
|
|
Will however follow also abstracts to their underlying implementation,
|
|
if they are not a @:coreType abstract
|
|
|
|
If `t` is null, an internal exception is thrown.
|
|
|
|
Usage example:
|
|
var t = Context.typeof(macro new Map<String, String>());
|
|
trace(t); // TAbstract(Map,[TInst(String,[]),TInst(String,[])])
|
|
trace(t.followWithAbstracts()); // TInst(haxe.ds.StringMap, [TInst(String,[])])
|
|
**/
|
|
static public inline function followWithAbstracts(t:Type, once:Bool = false):Type
|
|
return Context.followWithAbstracts(t, once);
|
|
|
|
/**
|
|
Returns true if `t1` and `t2` unify, false otherwise.
|
|
**/
|
|
static public inline function unify(t1:Type, t2:Type):Bool
|
|
return Context.unify(t1, t2);
|
|
|
|
/**
|
|
Tries to extract the class instance stored inside `t`.
|
|
|
|
If `t` is a class instance `TInst(c,pl)`, c is returned.
|
|
|
|
If `t` is of a different type, an exception of type String is thrown.
|
|
|
|
If `t` is null, the result is null.
|
|
**/
|
|
static public function getClass(t:Type)
|
|
return t == null ? null : switch (follow(t)) {
|
|
case TInst(c, _): c.get();
|
|
case _: throw "Class instance expected";
|
|
}
|
|
|
|
/**
|
|
Tries to extract the enum instance stored inside `t`.
|
|
|
|
If `t` is an enum instance `TEnum(e,pl)`, e is returned.
|
|
|
|
If `t` is of a different type, an exception of type String is thrown.
|
|
|
|
If `t` is null, the result is null.
|
|
**/
|
|
static public function getEnum(t:Type)
|
|
return t == null ? null : switch (follow(t)) {
|
|
case TEnum(e, _): e.get();
|
|
case _: throw "Enum instance expected";
|
|
}
|
|
|
|
/**
|
|
Applies the type parameters `typeParameters` to type `t` with the given
|
|
types `concreteTypes`.
|
|
|
|
This function replaces occurrences of type parameters in `t` if they are
|
|
part of `typeParameters`. The array index of such a type parameter is
|
|
then used to lookup the concrete type in `concreteTypes`.
|
|
|
|
If `typeParameters.length` is not equal to `concreteTypes.length`, an
|
|
exception of type `String` is thrown.
|
|
|
|
If `typeParameters.length` is 0, `t` is returned unchanged.
|
|
|
|
If either argument is `null`, the result is unspecified.
|
|
**/
|
|
static public function applyTypeParameters(t:Type, typeParameters:Array<TypeParameter>, concreteTypes:Array<Type>):Type {
|
|
if (typeParameters.length != concreteTypes.length)
|
|
throw 'Incompatible arguments: ${typeParameters.length} type parameters and ${concreteTypes.length} concrete types';
|
|
else if (typeParameters.length == 0)
|
|
return t;
|
|
#if (neko || eval)
|
|
return @:privateAccess Context.load("apply_params", 3)(typeParameters, concreteTypes, t);
|
|
#else
|
|
return applyParams(typeParameters, concreteTypes, t);
|
|
#end
|
|
}
|
|
|
|
#if !neko
|
|
private static function applyParams(typeParameters:Array<TypeParameter>, concreteTypes:Array<Type>, t:Type):Type {
|
|
return null;
|
|
}
|
|
#end
|
|
|
|
/**
|
|
Transforms `t` by calling `f` on each of its subtypes.
|
|
|
|
If `t` is a compound type, `f` is called on each of its components.
|
|
|
|
Otherwise `t` is returned unchanged.
|
|
|
|
The following types are considered compound:
|
|
- TInst, TEnum, TType and TAbstract with type parameters
|
|
- TFun
|
|
- TAnonymous
|
|
|
|
If `t` or `f` are null, the result is unspecified.
|
|
**/
|
|
static public function map(t:Type, f:Type->Type):Type {
|
|
return switch (t) {
|
|
case TMono(tm):
|
|
switch (tm.get()) {
|
|
case null: t;
|
|
case var t: f(t);
|
|
}
|
|
case TEnum(_, []) | TInst(_, []) | TType(_, []):
|
|
t;
|
|
case TEnum(en, tl):
|
|
TEnum(en, tl.map(f));
|
|
case TInst(cl, tl):
|
|
TInst(cl, tl.map(f));
|
|
case TType(t2, tl):
|
|
TType(t2, tl.map(f));
|
|
case TAbstract(a, tl):
|
|
TAbstract(a, tl.map(f));
|
|
case TFun(args, ret):
|
|
TFun(args.map(function(arg) return {
|
|
name: arg.name,
|
|
opt: arg.opt,
|
|
t: f(arg.t)
|
|
}), f(ret));
|
|
case TAnonymous(an):
|
|
TAnonymous(@:privateAccess Context.load("map_anon_ref", 2)(an, f));
|
|
case TDynamic(t2):
|
|
t == t2 ? t : TDynamic(f(t2));
|
|
case TLazy(ft):
|
|
var ft = ft();
|
|
var ft2 = f(ft);
|
|
ft == ft2 ? t : ft2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Calls function `f` on each component of type `t`.
|
|
|
|
If `t` is not a compound type, this operation has no effect.
|
|
|
|
The following types are considered compound:
|
|
- TInst, TEnum, TType and TAbstract with type parameters
|
|
- TFun
|
|
- TAnonymous
|
|
|
|
If `t` or `f` are null, the result is unspecified.
|
|
**/
|
|
static public function iter(t:Type, f:Type->Void):Void {
|
|
switch (t) {
|
|
case TMono(tm):
|
|
var t = tm.get();
|
|
if (t != null)
|
|
f(t);
|
|
case TEnum(_, tl) | TInst(_, tl) | TType(_, tl) | TAbstract(_, tl):
|
|
for (t in tl)
|
|
f(t);
|
|
case TDynamic(t2):
|
|
if (t != t2)
|
|
f(t2);
|
|
case TLazy(ft):
|
|
f(ft());
|
|
case TAnonymous(an):
|
|
for (field in an.get().fields)
|
|
f(field.type);
|
|
case TFun(args, ret):
|
|
for (arg in args)
|
|
f(arg.t);
|
|
f(ret);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Converts type `t` to a human-readable String representation.
|
|
**/
|
|
static public function toString(t:Type):String {
|
|
#if (neko || eval)
|
|
return @:privateAccess Context.load("s_type", 1)(t);
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Changes the name of the variable in the typed expression.
|
|
**/
|
|
static public function setVarName(t:TVar, name:String) {
|
|
@:privateAccess Context.load("set_var_name", 2)(t, name);
|
|
}
|
|
|
|
/**
|
|
Converts type `t` to `ModuleType`.
|
|
**/
|
|
static public function toModuleType(t:Type):ModuleType {
|
|
#if (neko || eval)
|
|
return @:privateAccess Context.load("type_to_module_type", 1)(t);
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
/**
|
|
Creates a type from the `ModuleType` argument.
|
|
**/
|
|
static public function fromModuleType(mt:ModuleType):Type {
|
|
#if (neko || eval)
|
|
return @:privateAccess Context.load("module_type_to_type", 1)(mt);
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
#end
|
|
|
|
/**
|
|
Resolves the field named `name` on class `c`.
|
|
|
|
If `isStatic` is true, the classes' static fields are checked. Otherwise
|
|
the classes' member fields are checked.
|
|
|
|
If the field is found, it is returned. Otherwise if `c` has a super
|
|
class, `findField` recursively checks that super class. Otherwise null
|
|
is returned.
|
|
|
|
If any argument is null, the result is unspecified.
|
|
**/
|
|
static public function findField(c:ClassType, name:String, isStatic:Bool = false):Null<ClassField> {
|
|
var field = (isStatic ? c.statics : c.fields).get().find(function(field) return field.name == name);
|
|
return if (field != null) field; else if (c.superClass != null) findField(c.superClass.t.get(), name, isStatic); else null;
|
|
}
|
|
}
|