mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-12-08 13:08:26 +00:00
Blacklisted class macro
Macro that automatically generates fields for use in sandboxed classes.
This commit is contained in:
parent
3f89464608
commit
a2ccb5d44a
|
|
@ -1,13 +1,41 @@
|
|||
package funkin.util;
|
||||
|
||||
import Type.ValueType;
|
||||
|
||||
/**
|
||||
* Provides sanitized and blacklisted access to haxe's Reflection functions.
|
||||
* Used for sandboxing in scripts.
|
||||
*/
|
||||
@:nullSafety
|
||||
@SuppressWarnings("checkstyle:VarTypeHint")
|
||||
@:build(funkin.util.macro.BlacklistClassMacro.build(
|
||||
{
|
||||
classes: ["Reflect", "Type"],
|
||||
aliases:
|
||||
{
|
||||
"compare": ["compareValues"],
|
||||
"copy": ["copyAnonymousFieldsOf"],
|
||||
"deleteField": ["deleteAnonymousField", "delete"],
|
||||
"field": ["getAnonymousField", "getField"],
|
||||
"fields": ["getAnonymousFieldsOf", "getFieldsOf"],
|
||||
"hasField": ["hasAnonymousField"],
|
||||
"setField": ["setAnonymousField"]
|
||||
},
|
||||
customWrapList: [
|
||||
"compare",
|
||||
"compareMethods",
|
||||
"copy",
|
||||
"enumEq",
|
||||
"deleteField",
|
||||
"fields",
|
||||
"getClassFields",
|
||||
"getClassName",
|
||||
"getEnumName",
|
||||
"getInstanceFields",
|
||||
"isEnumValue",
|
||||
"isFunction",
|
||||
"isObject",
|
||||
"setField",
|
||||
"setProperty"
|
||||
]
|
||||
}))
|
||||
class ReflectUtil
|
||||
{
|
||||
/**
|
||||
|
|
@ -20,24 +48,16 @@ class ReflectUtil
|
|||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function callMethod(obj:Any, name:String, args:Array<Any>):Any
|
||||
{
|
||||
throw "Function Reflect.callMethod is blacklisted.";
|
||||
}
|
||||
|
||||
// public static function callMethod(obj:Any, name:String, args:Array<Any>):Any
|
||||
/**
|
||||
* Compares two objects by value.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param valueA First value to compare
|
||||
* @param valueB Second value to compare
|
||||
* @return Int indicating relative order of values
|
||||
*/
|
||||
public static function compare(valueA:Any, valueB:Any):Int
|
||||
{
|
||||
return compareValues(valueA, valueB);
|
||||
}
|
||||
|
||||
// public static function compare(valueA:Any, valueB:Any):Int
|
||||
/**
|
||||
* Compares two values and returns an integer indicating their relative order.
|
||||
* Returns:
|
||||
|
|
@ -45,94 +65,76 @@ class ReflectUtil
|
|||
* - 0 if valueA == valueB
|
||||
* - 1 if valueA > valueB
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param valueA First value to compare
|
||||
* @param valueB Second value to compare
|
||||
* @return An integer indicating relative order of values
|
||||
*/
|
||||
public static function compareValues(valueA:Any, valueB:Any):Int
|
||||
{
|
||||
return Reflect.compare(valueA, valueB);
|
||||
}
|
||||
|
||||
// public static function compareValues(valueA:Any, valueB:Any):Int
|
||||
/**
|
||||
* Compare the two Function objects to determine whether they are the same.
|
||||
* @param functionA A method closure to compare.
|
||||
* @param functionB A method closure to compare.
|
||||
* @return Whether functionA and functionB are equal.
|
||||
*/
|
||||
public static function compareMethods(functionA:Any, functionB:Any):Bool
|
||||
{
|
||||
return Reflect.compareMethods(functionA, functionB);
|
||||
}
|
||||
|
||||
// public static function compareMethods(functionA:Any, functionB:Any):Bool
|
||||
/**
|
||||
* Copies the given object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to copy.
|
||||
* @return An independent clone of that object.
|
||||
*/
|
||||
public static function copy(obj:Any):Null<Any>
|
||||
{
|
||||
return copyAnonymousFieldsOf(obj);
|
||||
}
|
||||
|
||||
// public static function copy(obj:Any):Null<Any>
|
||||
/**
|
||||
* Copies the anonymous structure to a new object.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to copy.
|
||||
* @return An independent clone of the structure.
|
||||
*/
|
||||
public static function copyAnonymousFieldsOf(obj:Any):Null<Any>
|
||||
{
|
||||
return Reflect.copy(obj);
|
||||
}
|
||||
|
||||
// public static function copyAnonymousFieldsOf(obj:Any):Null<Any>
|
||||
/**
|
||||
* Delete the field of a given name from an object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function delete(obj:Any, name:String):Bool
|
||||
{
|
||||
return deleteAnonymousField(obj, name);
|
||||
}
|
||||
|
||||
// public static function delete(obj:Any, name:String):Bool
|
||||
/**
|
||||
* Delete the field of a given name from an anonymous structure.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function deleteAnonymousField(obj:Any, name:String):Bool
|
||||
{
|
||||
return Reflect.deleteField(obj, name);
|
||||
}
|
||||
|
||||
// public static function deleteAnonymousField(obj:Any, name:String):Bool
|
||||
/**
|
||||
* Retrive the value of a given field (by name) from an object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function field(obj:Any, name:String):Any
|
||||
{
|
||||
return getAnonymousField(obj, name);
|
||||
}
|
||||
|
||||
// public static function field(obj:Any, name:String):Any
|
||||
/**
|
||||
* Retrive the value of a given field (by name) from an object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function getField(obj:Any, name:String):Any
|
||||
{
|
||||
return getAnonymousField(obj, name);
|
||||
}
|
||||
// public static function getField(obj:Any, name:String):Any
|
||||
|
||||
/**
|
||||
* Retrieve the value of the field of the given name from an anonymous structure.
|
||||
|
|
@ -141,7 +143,8 @@ class ReflectUtil
|
|||
* @return The resulting field value.
|
||||
* @throws error If the field is blacklisted.
|
||||
*/
|
||||
public static function getAnonymousField(obj:Any, name:String):Any
|
||||
@:blacklistOverride
|
||||
public static function field(obj:Any, name:String):Any
|
||||
{
|
||||
if (FIELD_NAME_BLACKLIST.contains(name))
|
||||
{
|
||||
|
|
@ -154,34 +157,29 @@ class ReflectUtil
|
|||
/**
|
||||
* Get a list of fields available on the given object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @return A list of fields on that object.
|
||||
*/
|
||||
public static function fields(obj:Any):Array<String>
|
||||
{
|
||||
return getAnonymousFieldsOf(obj);
|
||||
}
|
||||
|
||||
// public static function fields(obj:Any):Array<String>
|
||||
/**
|
||||
* Get a list of fields available on the given object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @return A list of fields on that object.
|
||||
*/
|
||||
public static function getFieldsOf(obj:Any):Array<String>
|
||||
{
|
||||
return getAnonymousFieldsOf(obj);
|
||||
}
|
||||
|
||||
// public static function getFieldsOf(obj:Any):Array<String>
|
||||
/**
|
||||
* Get a list of fields available on the given anonymous structure.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @return A list of fields on that object.
|
||||
*/
|
||||
public static function getAnonymousFieldsOf(obj:Any):Array<String>
|
||||
{
|
||||
return Reflect.fields(obj);
|
||||
}
|
||||
// public static function getAnonymousFieldsOf(obj:Any):Array<String>
|
||||
|
||||
/**
|
||||
* Get the value of the given property on a given object.
|
||||
|
|
@ -192,6 +190,7 @@ class ReflectUtil
|
|||
* @return The value of the field.
|
||||
* @throws error If the field is blacklisted.
|
||||
*/
|
||||
@:blacklistOverride
|
||||
public static function getProperty(obj:Any, name:String):Any
|
||||
{
|
||||
if (FIELD_NAME_BLACKLIST.contains(name))
|
||||
|
|
@ -205,14 +204,13 @@ class ReflectUtil
|
|||
/**
|
||||
* Determine whether the given object has the given field.
|
||||
* Only guaranteed to work for anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @param name The field name to query.
|
||||
* @return Whether the field exists.
|
||||
*/
|
||||
public static function hasField(obj:Any, name:String):Bool
|
||||
{
|
||||
return hasAnonymousField(obj, name);
|
||||
}
|
||||
// public static function hasField(obj:Any, name:String):Bool
|
||||
|
||||
/**
|
||||
* Determine whether the given anonymous structure has the given field.
|
||||
|
|
@ -220,7 +218,8 @@ class ReflectUtil
|
|||
* @param name The field name to query.
|
||||
* @return Whether the field exists.
|
||||
*/
|
||||
public static function hasAnonymousField(obj:Any, name:String):Bool
|
||||
@:blacklistOverride
|
||||
public static function hasField(obj:Any, name:String):Bool
|
||||
{
|
||||
if (FIELD_NAME_BLACKLIST.contains(name))
|
||||
{
|
||||
|
|
@ -232,128 +231,89 @@ class ReflectUtil
|
|||
|
||||
/**
|
||||
* Determine whether the given input is an enum value.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param value The input to evaluate.
|
||||
* @return Whether `value` is an enum value.
|
||||
*/
|
||||
public static function isEnumValue(value:Any):Bool
|
||||
{
|
||||
return Reflect.isEnumValue(value);
|
||||
}
|
||||
|
||||
// public static function isEnumValue(value:Any):Bool
|
||||
/**
|
||||
* Determine whether the given input is a callable function.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param value The input to evaluate.
|
||||
* @return Whether `value` is a function.
|
||||
*/
|
||||
public static function isFunction(value:Any):Bool
|
||||
{
|
||||
return Reflect.isFunction(value);
|
||||
}
|
||||
|
||||
// public static function isFunction(value:Any):Bool
|
||||
/**
|
||||
* Determine whether the given input is an object.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param value The input to evaluate.
|
||||
* @return Whether `value` is an object.
|
||||
*/
|
||||
public static function isObject(value:Any):Bool
|
||||
{
|
||||
return Reflect.isObject(value);
|
||||
}
|
||||
|
||||
// public static function isObject(value:Any):Bool
|
||||
/**
|
||||
* Set the value of a specific field on an object.
|
||||
* Only guaranteed to work for anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to modify.
|
||||
* @param name The field to modify.
|
||||
* @param value The new value to apply.
|
||||
*/
|
||||
public static function setField(obj:Any, name:String, value:Any):Void
|
||||
{
|
||||
return setAnonymousField(obj, name, value);
|
||||
}
|
||||
|
||||
// public static function setField(obj:Any, name:String, value:Any):Void
|
||||
/**
|
||||
* Set the value of a specific field on an anonymous structure.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to modify.
|
||||
* @param name The field to modify.
|
||||
* @param value The new value to apply.
|
||||
*/
|
||||
public static function setAnonymousField(obj:Any, name:String, value:Any):Void
|
||||
{
|
||||
return Reflect.setField(obj, name, value);
|
||||
}
|
||||
|
||||
// public static function setAnonymousField(obj:Any, name:String, value:Any):Void
|
||||
/**
|
||||
* Set the value of a specific field on an object.
|
||||
* Accounts for property fields with getters and setters.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to modify.
|
||||
* @param name The field to modify.
|
||||
* @param value The new value to apply.
|
||||
*/
|
||||
public static function setProperty(obj:Any, name:String, value:Any):Void
|
||||
{
|
||||
return Reflect.setProperty(obj, name, value);
|
||||
}
|
||||
|
||||
// public static function setProperty(obj:Any, name:String, value:Any):Void
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
// public static function createEmptyInstance(cls:Class<Any>):Any
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
// public static function createInstance(cls:Class<Any>, args:Array<Any>):Any
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function createEmptyInstance(cls:Class<Any>):Any
|
||||
{
|
||||
throw "Function Type.createEmptyInstance is blacklisted.";
|
||||
}
|
||||
|
||||
// public static function resolveEnum(name:String):Enum<Any>
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function createInstance(cls:Class<Any>, args:Array<Any>):Any
|
||||
{
|
||||
throw "Function Type.createInstance is blacklisted.";
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function resolveClass(name:String):Class<Any>
|
||||
{
|
||||
throw "Function Type.resolveClass is blacklisted.";
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function resolveEnum(name:String):Enum<Any>
|
||||
{
|
||||
throw "Function Type.resolveEnum is blacklisted.";
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function typeof(value:Any):ValueType
|
||||
{
|
||||
throw "Function Type.typeof is blacklisted.";
|
||||
}
|
||||
|
||||
// public static function typeof(value:Any):ValueType
|
||||
/**
|
||||
* Get a list of the static class fields on the given class.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param cls The class object to query.
|
||||
* @return A list of class field names.
|
||||
*/
|
||||
public static function getClassFields(cls:Class<Any>):Array<String>
|
||||
{
|
||||
return Type.getClassFields(cls);
|
||||
}
|
||||
// public static function getClassFields(cls:Class<Any>):Array<String>
|
||||
|
||||
/**
|
||||
* Get a list of the static class fields on the class of the given object.
|
||||
|
|
@ -371,13 +331,12 @@ class ReflectUtil
|
|||
|
||||
/**
|
||||
* Get a list of all the fields on instances of the given class.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param cls The class object to query.
|
||||
* @return A list of object field names.
|
||||
*/
|
||||
public static function getInstanceFields(cls:Class<Any>):Array<String>
|
||||
{
|
||||
return Type.getInstanceFields(cls);
|
||||
}
|
||||
// public static function getInstanceFields(cls:Class<Any>):Array<String>
|
||||
|
||||
/**
|
||||
* Get a list of all the fields on instances of the class of the given object.
|
||||
|
|
@ -395,13 +354,12 @@ class ReflectUtil
|
|||
|
||||
/**
|
||||
* Get the string name of the given class.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param cls The class to query.
|
||||
* @return The name of the given class.
|
||||
*/
|
||||
public static function getClassName(cls:Class<Any>):String
|
||||
{
|
||||
return Type.getClassName(cls);
|
||||
}
|
||||
// public static function getClassName(cls:Class<Any>):String
|
||||
|
||||
/**
|
||||
* Get the string name of the class of the given object.
|
||||
|
|
|
|||
295
source/funkin/util/macro/BlacklistClassMacro.hx
Normal file
295
source/funkin/util/macro/BlacklistClassMacro.hx
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
package funkin.util.macro;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Expr.Field;
|
||||
import haxe.macro.Type;
|
||||
import haxe.DynamicAccess;
|
||||
|
||||
using haxe.macro.TypeTools;
|
||||
using haxe.macro.ComplexTypeTools;
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
||||
enum abstract WrapMode(String) from String to String
|
||||
{
|
||||
var Blacklist;
|
||||
var Whitelist;
|
||||
}
|
||||
|
||||
typedef WrapperParams =
|
||||
{
|
||||
/**
|
||||
* Classes to generate functions for.
|
||||
*/
|
||||
var classes:Array<String>;
|
||||
|
||||
/**
|
||||
* Aliases to functions, for instance `{ "getAnonymousField": ["field"] }` generates a `field` function that calls `getAnonymousField`.
|
||||
* It works in both ways, so you can generate aliases in the class that point to the ones in `classes` and vice-versa.
|
||||
* This should be strictly structured as `{ fieldName: Array<String> }`, failure to comply will result in undefined behaviour.
|
||||
*/
|
||||
@:optional
|
||||
var aliases:{};
|
||||
|
||||
/**
|
||||
* Functions that should be wrapped based on the value of `customWrapMode`.
|
||||
* If a function is part of an alias or the ignore list, this will have no effect on it.
|
||||
*/
|
||||
@:optional
|
||||
var customWrapList:Array<String>;
|
||||
|
||||
/**
|
||||
* Defines how fields inside and outside of `customWrapList` are wrapped.
|
||||
* If it's `Whitelist` fields outside the list are blacklisted by default.
|
||||
*
|
||||
* @default `Whitelist`
|
||||
*/
|
||||
@:optional
|
||||
var customWrapMode:WrapMode;
|
||||
|
||||
/**
|
||||
* Functions in `classes` that the macro should not generate.
|
||||
* If a function is part of an alias, this will have no effect on it.
|
||||
*/
|
||||
@:optional
|
||||
var ignoreList:Array<String>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates fields that wrap functions from the provided classes in a way that
|
||||
* they'll throw an error if accessed, or call the original function if whitelisted.
|
||||
* It is best to be used with classes with only static fields. Private fields and variables are always ignored.
|
||||
*
|
||||
* You can add your own sandboxed implementations of the fields and make aliases to them (see `BlacklistParams.aliases`).
|
||||
* Note that if the field already exists in `BlacklistParams.classes` you should add `@:blacklistOverride` to it.
|
||||
*/
|
||||
class BlacklistClassMacro
|
||||
{
|
||||
/**
|
||||
* Documentation used by blacklisted functions.
|
||||
*/
|
||||
static final BLACKLISTED_FUNCTION_DOC:String = "This function is not allowed to be used by scripts.\n@throws error When called by a script.";
|
||||
|
||||
static var buildFields:Array<Field>;
|
||||
static var processedFieldNames:Array<String> = [];
|
||||
|
||||
static inline function containsField(fieldName:String):Bool
|
||||
{
|
||||
return buildFields.exists(f -> f.name == fieldName);
|
||||
}
|
||||
|
||||
static inline function getField(fieldName:String):Null<Field>
|
||||
{
|
||||
return buildFields.find(f -> f.name == fieldName);
|
||||
}
|
||||
|
||||
static function build(params:WrapperParams):Array<Field>
|
||||
{
|
||||
final classes:Array<ClassType> = [for (c in params.classes) MacroUtil.getClassType(c)];
|
||||
if (classes.length == 0) Context.fatalError('Invalid class amount, no classes were provided.', Context.currentPos());
|
||||
|
||||
buildFields = Context.getBuildFields();
|
||||
var generatedFields:Array<Field> = [];
|
||||
|
||||
params.customWrapList ??= [];
|
||||
params.customWrapMode ??= Whitelist;
|
||||
|
||||
// NOTE: As much as I wish these could be a map seems like Haxe is unable to parse them as part of the metadata.
|
||||
final aliases:DynamicAccess<Array<String>> = cast params.aliases;
|
||||
var fieldsToSkip:Array<String> = params.ignoreList?.copy() ?? [];
|
||||
var pendingFieldsToWrap:Array<String> = [];
|
||||
|
||||
if (aliases != null)
|
||||
{
|
||||
generatedFields = generateAliases(aliases, pendingFieldsToWrap);
|
||||
}
|
||||
|
||||
for (c in classes)
|
||||
{
|
||||
for (field in c.statics.get())
|
||||
{
|
||||
if (!field.isPublic || fieldsToSkip.contains(field.name) || ~/^(get|set)_/.match(field.name)) continue;
|
||||
|
||||
if (containsField(field.name))
|
||||
{
|
||||
if (!getField(field.name).meta.exists(m -> m.name == ':blacklistOverride'))
|
||||
{
|
||||
// 'reportError' doesn't abort compilation, so it allows us to see all the duplicate fields!
|
||||
Context.reportError('Tried to generate "${field.name}" but it already exists in the class.\n'
|
||||
+ 'Add @:blacklistOverride or add it to "ignoreList" to ignore.',
|
||||
getField(field.name).pos);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
final blacklisted:Bool = (params.customWrapMode == Whitelist) != params.customWrapList.contains(field.name);
|
||||
final wrapper:Null<Field> = generateWrapperField(field.name, field, c.name, blacklisted);
|
||||
if (wrapper == null) continue; // Not a function
|
||||
|
||||
generatedFields.push(wrapper);
|
||||
// TODO: When this happens should it make the field whitelisted (or vice-versa)?
|
||||
if (pendingFieldsToWrap.contains(field.name))
|
||||
{
|
||||
for (alias in aliases.get(field.name))
|
||||
{
|
||||
generatedFields.push(generateWrapperField(alias, wrapper));
|
||||
pendingFieldsToWrap.remove(field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (f in pendingFieldsToWrap)
|
||||
{
|
||||
Context.reportError('Tried to generate alias fields for "$f" but it does not exist.', Context.currentPos());
|
||||
}
|
||||
|
||||
return buildFields.concat(generatedFields);
|
||||
}
|
||||
|
||||
static function generateAliases(aliases:DynamicAccess<Array<String>>, ?unresolvedAliases:Array<String>):Array<Field>
|
||||
{
|
||||
var result:Array<Field> = [];
|
||||
|
||||
for (field => aliasFields in aliases)
|
||||
{
|
||||
if (aliasFields.length == 0) Context.warning('No alias fields specified to be generated for "$field"', Context.currentPos());
|
||||
|
||||
final wrappedField:Null<Field> = getField(field);
|
||||
if (wrappedField == null && unresolvedAliases != null)
|
||||
{
|
||||
// Field might be on the provided classes, put it on queue.
|
||||
unresolvedAliases.push(field);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (aliasName in aliasFields)
|
||||
{
|
||||
if (containsField(aliasName))
|
||||
{
|
||||
Context.error('Tried to generate "${aliasName}" alias but it already exists in the class.', getField(aliasName).pos);
|
||||
}
|
||||
|
||||
final wrapper:Null<Field> = generateWrapperField(aliasName, wrappedField);
|
||||
if (wrapper != null)
|
||||
{
|
||||
result.push(wrapper);
|
||||
processedFieldNames.push(aliasName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.error('Could not generate alias for field "$field"; it may not be a function.', wrappedField.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function generateWrapperField(fieldName:String, wrappedField:Dynamic, ?className:String, blacklist:Bool = false):Null<Field>
|
||||
{
|
||||
final pack:Array<String> = [wrappedField.name];
|
||||
if (className != null) pack.unshift(className);
|
||||
|
||||
function getWrapperExpr(args:Array<{name:String}>, ?retType:ComplexType):Expr
|
||||
{
|
||||
return if (blacklist)
|
||||
{
|
||||
macro throw $v{'Function ${pack.join('.')} is blacklisted.'};
|
||||
}
|
||||
else
|
||||
{
|
||||
final params:Array<Expr> = [for (a in args) macro $i{a.name}];
|
||||
retType.toString() == 'StdTypes.Void' ? macro $p{pack}($a{params}) : macro return $p{pack}($a{params});
|
||||
}
|
||||
}
|
||||
|
||||
var wrapperKind:Null<FieldType>;
|
||||
if (wrappedField.kind is FieldType)
|
||||
{
|
||||
wrapperKind = switch (wrappedField.kind)
|
||||
{
|
||||
case FFun(f):
|
||||
final wrapFunc:Function = Reflect.copy(f);
|
||||
wrapFunc.expr = getWrapperExpr(wrapFunc.args, wrapFunc.ret);
|
||||
FFun(wrapFunc);
|
||||
default:
|
||||
Context.error('Blacklist Macro: Making wrappers for anything other than functions is not supported.', wrappedField.pos);
|
||||
}
|
||||
}
|
||||
else if (wrappedField.expr() != null)
|
||||
{
|
||||
switch (wrappedField.expr().expr)
|
||||
{
|
||||
case TFunction(tfunc):
|
||||
final args:Array<FunctionArg> = [
|
||||
for (a in tfunc.args)
|
||||
{
|
||||
name: a.v.name,
|
||||
value: a.value != null ? Context.getTypedExpr(a.value) : null,
|
||||
type: a.v.t.toComplexType()
|
||||
}
|
||||
];
|
||||
wrapperKind = FFun(
|
||||
{
|
||||
args: args,
|
||||
params: getParamDecls(wrappedField.params),
|
||||
ret: tfunc.t.toComplexType(),
|
||||
expr: getWrapperExpr(args, tfunc.t.toComplexType()),
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some targets have core types with externs as functions and those don't have a TypedExpr.
|
||||
// We follow its type once to get rid of any lazy type
|
||||
switch (wrappedField.type.follow(true))
|
||||
{
|
||||
case TFun(args, ret):
|
||||
wrapperKind = FFun(
|
||||
{
|
||||
args: [for (a in args) {name: a.name, opt: a.opt, type: a.t.toComplexType()}],
|
||||
params: getParamDecls(wrappedField.params),
|
||||
ret: ret.toComplexType(),
|
||||
expr: getWrapperExpr(args, ret.toComplexType())
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final access = [APublic, AStatic];
|
||||
if (wrapperKind.match(FFun(_)))
|
||||
{
|
||||
access.push(AInline);
|
||||
}
|
||||
|
||||
return {
|
||||
name: fieldName,
|
||||
pos: wrappedField.pos,
|
||||
doc: blacklist ? BLACKLISTED_FUNCTION_DOC : wrappedField.doc,
|
||||
access: access,
|
||||
kind: wrapperKind
|
||||
};
|
||||
}
|
||||
|
||||
static function getParamDecls(params:Array<TypeParameter>):Array<TypeParamDecl>
|
||||
{
|
||||
final result:Array<TypeParamDecl> = [];
|
||||
for (p in params)
|
||||
{
|
||||
switch (p.t.getClass()?.kind)
|
||||
{
|
||||
case KTypeParameter(constraints):
|
||||
result.push({name: p.name, constraints: [for (c in constraints) c.toComplexType()]});
|
||||
default:
|
||||
Context.error("Provided type parameters are not of the KTypeParameter kind, this shouldn't happen!", Context.currentPos());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#end
|
||||
Loading…
Reference in a new issue