Skip to content

Commit d17614a

Browse files
committed
WIP on property overrides
1 parent ac7e088 commit d17614a

File tree

2 files changed

+83
-62
lines changed

2 files changed

+83
-62
lines changed

source/funkin/util/ReflectUtil.hx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ package funkin.util;
1818
"hasField": ["hasAnonymousField"],
1919
"setField": ["setAnonymousField"]
2020
},
21-
fieldList: [
21+
customWrapList: [
2222
"compare",
2323
"compareMethods",
24+
"copy",
2425
"enumEq",
2526
"deleteField",
2627
"fields",

source/funkin/util/macro/BlacklistClassMacro.hx

Lines changed: 81 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ using haxe.macro.ComplexTypeTools;
1212
using Lambda;
1313
using StringTools;
1414

15-
typedef Either<T, V> = Dynamic;
15+
enum abstract WrapMode(String) from String to String
16+
{
17+
var Blacklist;
18+
var Whitelist;
19+
}
1620

1721
typedef WrapperParams =
1822
{
@@ -22,37 +26,40 @@ typedef WrapperParams =
2226
var classes:Array<String>;
2327

2428
/**
25-
* Aliases to functions, for instance `{ "getAnonymousField": ["field"]] }` generates a `field` function that calls `getAnonymousField`.
29+
* Aliases to functions, for instance `{ "getAnonymousField": ["field"] }` generates a `field` function that calls `getAnonymousField`.
2630
* It works in both ways, so you can generate aliases in the class that point to the ones in `classes` and vice-versa.
27-
* This should be strictly structured as { fieldName: Array<String> }, failure to comply will result in undefined behaviour.
31+
* This should be strictly structured as `{ fieldName: Array<String> }`, failure to comply will result in undefined behaviour.
2832
*/
2933
var ?aliases:{};
3034

3135
/**
32-
* Functions that should be wrapped based on `whitelistMode`.
36+
* Functions that should be wrapped based on `wrapMode`.
3337
* If a function is part of an alias or the ignore list, this will have no effect on it.
3438
*/
35-
var ?fieldList:Array<String>;
39+
var ?customWrapList:Array<String>;
3640

3741
/**
38-
* Defines how fields in `fieldList` are wrapped.
39-
* By default it's `true`, which means fields outside the list are blacklisted by default.
42+
* Defines how fields in `customWrapList` are wrapped.
43+
* If it's `Whitelist` fields outside the list are blacklisted by default.
44+
*
45+
* @default `Whitelist`
4046
*/
41-
var ?whitelistMode:Bool;
47+
var ?customWrapMode:WrapMode;
4248

4349
/**
44-
* Functions in the provided classes that the macro should skip generating.
50+
* Functions in `classes` that the macro should not generate.
4551
* If a function is part of an alias, this will have no effect on it.
4652
*/
4753
var ?ignoreList:Array<String>;
4854
}
4955

5056
/**
5157
* Generates fields that wrap functions from the provided classes in a way that
52-
* they'll throw an error if accessed or call the original function if whitelisted.
58+
* they'll throw an error if accessed, or call the original function if whitelisted.
5359
* It is best to be used with classes with only static fields.
5460
*
5561
* You can add your own sandboxed implementations of the fields and make aliases to them (see `BlacklistParams.aliases`).
62+
* Note that if the field already exists in `BlacklistParams.classes` you should add `@:blacklistOverride` to it.
5663
*/
5764
class BlacklistClassMacro
5865
{
@@ -61,69 +68,45 @@ class BlacklistClassMacro
6168
*/
6269
static final BLACKLISTED_FUNCTION_DOC:String = "This function is not allowed to be used by scripts.\n@throws error When called by a script.";
6370

64-
static function build(params:WrapperParams):Array<Field>
71+
static var buildFields:Array<Field>;
72+
static var processedFieldNames:Array<String> = [];
73+
74+
static inline function containsField(fieldName:String):Bool
6575
{
66-
params.whitelistMode ??= true;
67-
params.fieldList ??= [];
76+
return buildFields.exists(f -> f.name == fieldName);
77+
}
6878

79+
static inline function getField(fieldName:String):Null<Field>
80+
{
81+
return buildFields.find(f -> f.name == fieldName);
82+
}
83+
84+
static function build(params:WrapperParams):Array<Field>
85+
{
6986
final classes:Array<ClassType> = [for (c in params.classes) MacroUtil.getClassType(c)];
7087
if (classes.length == 0) Context.fatalError('Invalid class amount, no classes were provided.', Context.currentPos());
7188

72-
final buildFields:Array<Field> = Context.getBuildFields();
73-
final generatedFields:Array<Field> = [];
89+
buildFields = Context.getBuildFields();
90+
var generatedFields:Array<Field> = [];
7491

75-
// For convenience...
76-
inline function containsField(fieldName:String):Bool
77-
{
78-
return buildFields.exists(f -> f.name == fieldName);
79-
}
80-
inline function getField(fieldName:String):Null<Field>
81-
{
82-
return buildFields.find(f -> f.name == fieldName);
83-
}
92+
params.customWrapList ??= [];
93+
params.customWrapMode ??= Whitelist;
8494

8595
// NOTE: As much as I wish these could be a map seems like Haxe is unable to parse them as part of the metadata.
8696
final aliases:DynamicAccess<Array<String>> = cast params.aliases;
87-
final fieldsToSkip:Array<String> = params.ignoreList?.copy() ?? [];
88-
final pendingFieldsToWrap:Array<String> = [];
97+
var fieldsToSkip:Array<String> = params.ignoreList?.copy() ?? [];
98+
var pendingFieldsToWrap:Array<String> = [];
99+
89100
if (aliases != null)
90101
{
91-
for (field => aliasFields in aliases)
92-
{
93-
if (aliasFields.length == 0) Context.warning('No alias fields specified to be generated for "$field"', Context.currentPos());
94-
95-
final wrappedField:Null<Field> = getField(field);
96-
if (wrappedField == null)
97-
{
98-
// Field might be on the provided classes, put it on queue.
99-
pendingFieldsToWrap.push(field);
100-
continue;
101-
}
102-
103-
for (aliasName in aliasFields)
104-
{
105-
if (containsField(aliasName))
106-
{
107-
Context.error('Tried to generate "${aliasName}" alias but it already exists in the class.', getField(aliasName).pos);
108-
}
109-
110-
final wrapper:Null<Field> = generateWrapperField(aliasName, wrappedField);
111-
if (wrapper != null)
112-
{
113-
generatedFields.push(wrapper);
114-
fieldsToSkip.push(aliasName);
115-
}
116-
else
117-
Context.error('Could not generate alias for field "$field"; it may not be a function.', wrappedField.pos);
118-
}
119-
}
102+
generatedFields = generateAliases(aliases, pendingFieldsToWrap);
120103
}
121104

122105
for (c in classes)
123106
{
124107
for (field in c.statics.get())
125108
{
126-
if (!field.isPublic || fieldsToSkip.contains(field.name)) continue;
109+
if (!field.isPublic || fieldsToSkip.contains(field.name) || ~/^(get|set)_/.match(field.name)) continue;
127110

128111
if (containsField(field.name))
129112
{
@@ -136,8 +119,7 @@ class BlacklistClassMacro
136119
}
137120
continue;
138121
}
139-
140-
final blacklisted:Bool = params.whitelistMode != params.fieldList.contains(field.name);
122+
final blacklisted:Bool = (params.customWrapMode == Whitelist) != params.customWrapList.contains(field.name);
141123
final wrapper:Null<Field> = generateWrapperField(field.name, field, c.name, blacklisted);
142124
generatedFields.push(wrapper);
143125

@@ -161,12 +143,49 @@ class BlacklistClassMacro
161143
return buildFields.concat(generatedFields);
162144
}
163145

164-
static function generateWrapperField(fieldName:String, wrappedField:Either<Field, ClassField>, ?className:String, blacklist:Bool = false):Null<Field>
146+
static function generateAliases(aliases:DynamicAccess<Array<String>>, ?unresolvedAliases:Array<String>):Array<Field>
147+
{
148+
var result:Array<Field> = [];
149+
150+
for (field => aliasFields in aliases)
151+
{
152+
if (aliasFields.length == 0) Context.warning('No alias fields specified to be generated for "$field"', Context.currentPos());
153+
154+
final wrappedField:Null<Field> = getField(field);
155+
if (wrappedField == null)
156+
{
157+
// Field might be on the provided classes, put it on queue.
158+
unresolvedAliases.push(field);
159+
continue;
160+
}
161+
162+
for (aliasName in aliasFields)
163+
{
164+
if (containsField(aliasName))
165+
{
166+
Context.error('Tried to generate "${aliasName}" alias but it already exists in the class.', getField(aliasName).pos);
167+
}
168+
169+
final wrapper:Null<Field> = generateWrapperField(aliasName, wrappedField);
170+
if (wrapper != null)
171+
{
172+
result.push(wrapper);
173+
processedFieldNames.push(aliasName);
174+
}
175+
else
176+
Context.error('Could not generate alias for field "$field"; it may not be a function.', wrappedField.pos);
177+
}
178+
}
179+
180+
return result;
181+
}
182+
183+
static function generateWrapperField(fieldName:String, wrappedField:Dynamic, ?className:String, blacklist:Bool = false):Null<Field>
165184
{
166185
final pack:Array<String> = [wrappedField.name];
167186
if (className != null) pack.unshift(className);
168187

169-
inline function getWrapperExpr(args:Array<{name:String}>, ?retType:ComplexType):Expr
188+
function getWrapperExpr(args:Array<{name:String}>, ?retType:ComplexType):Expr
170189
{
171190
return if (blacklist)
172191
{
@@ -198,7 +217,8 @@ class BlacklistClassMacro
198217
var wrappedField:ClassField = wrappedField;
199218
switch (wrappedField.kind)
200219
{
201-
case FVar(_, _):
220+
case FVar(read, write):
221+
trace(read, write);
202222
wrapperKind = FProp('default', 'never', wrappedField.type.toComplexType(), macro $p{pack});
203223
default:
204224
}

0 commit comments

Comments
 (0)