From bfed94df0b3630ab4ab1d089a57db171e57d7438 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jun 2026 13:12:21 -0400 Subject: [PATCH 01/12] Improve error messages for invalid final access, plus some other fixes. --- polymod/hscript/_internal/Interp.hx | 29 +---- .../_internal/PolymodAbstractScriptClass.hx | 115 ++++++++---------- 2 files changed, 57 insertions(+), 87 deletions(-) diff --git a/polymod/hscript/_internal/Interp.hx b/polymod/hscript/_internal/Interp.hx index a1c45e8e..21d1fc5e 100644 --- a/polymod/hscript/_internal/Interp.hx +++ b/polymod/hscript/_internal/Interp.hx @@ -508,7 +508,7 @@ class Interp } } - private static function registerScriptClass(c:ClassDecl) + static function registerScriptClass(c:ClassDecl) { var name = Util.getFullClassName(c); @@ -775,7 +775,7 @@ class Interp if ((decl?.isfinal ?? false) && decl?.expr != null) { - error(EInvalidAccess(id)); + error(EInvalidFinalSet(id)); return null; } } @@ -976,7 +976,7 @@ class Interp var l = locals.get(id); var v:Dynamic = (l == null) ? resolve(id) : l.r; - if (l != null && l.isfinal && l.r != null) return error(EInvalidAccess(id)); + if (l != null && l.isfinal && l.r != null) return error(EInvalidFinalSet(id)); if (prefix) { v += delta; @@ -1480,7 +1480,7 @@ class Interp { case EField(e, f): var obj = expr(e); - if (obj == null) error(EInvalidAccess(f)); + if (obj == null) error(ENullObjectReference(f)); return fcall(obj, f, args); default: return call(null, expr(e), args); @@ -2294,22 +2294,6 @@ class Interp } error(EInvalidScriptedVarGet(f)); - - // var result = Reflect.getProperty(o, f); - // To save a bit of performance, we only query for the existence of the property - // if the value is reported as null, AND only in debug builds. - - // #if debug - // if (!Reflect.hasField(o, f)) - // { - // var propertyList = Type.getInstanceFields(Type.getClass(o)); - // if (propertyList.indexOf(f) == -1) - // { - // error(EInvalidScriptedVarGet(f)); - // } - // } - // #end - // return result; } #if (hl && haxe4) else if (Std.isOfType(o, Enum)) @@ -2442,9 +2426,6 @@ class Interp } error(EInvalidScriptedVarSet(f)); - - // Reflect.setProperty(o, f, v); - // return v; } try @@ -3011,4 +2992,4 @@ private class ArrayIterator { return a[pos++]; } -} \ No newline at end of file +} diff --git a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx index b3bcbab3..ef39574a 100644 --- a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx +++ b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx @@ -78,8 +78,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass } else if (this.superClass == null) { - // @:privateAccess this._interp.error(EInvalidAccess(name)); - throw 'field "$name" does not exist in script class ${this.fullyQualifiedName}"'; + @:privateAccess this._interp.error(EInvalidAccess(name)); } else if (Type.getClass(this.superClass) == null) { @@ -90,8 +89,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass } else { - // @:privateAccess this._interp.error(EInvalidAccess(name)); - throw 'field "$name" does not exist in script class ${this.fullyQualifiedName}" or super class "${Type.getClassName(Type.getClass(this.superClass))}"'; + @:privateAccess this._interp.error(EInvalidAccess(name)); } } else if (Std.isOfType(this.superClass, PolymodScriptClass)) @@ -114,7 +112,6 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass catch (e:String) { @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'"; } } } @@ -171,83 +168,75 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass @:op(a.b) public function fieldWrite(name:String, value:Dynamic):Dynamic { - switch (name) + if (this.findVar(name) != null) { - case _: - if (this.findVar(name) != null) - { - var decl = this.findVar(name, true); - if (decl.isfinal && decl.expr != null) // The variable already exists and has a set value. - { - throw "Invalid access to field " + name; - return null; - } + var decl = this.findVar(name, true); + if (decl.isfinal && decl.expr != null) // The variable already exists and has a set value. + { + @:privateAccess this._interp.error(EInvalidFinalSet(name)); + } - @:privateAccess - switch (decl.set) + @:privateAccess + switch (decl.set) + { + case "set": + final setName = 'set_$name'; + if (!this._interp._propTrack.exists(setName)) { - case "set": - final setName = 'set_$name'; - if (!this._interp._propTrack.exists(setName)) - { - this._interp._propTrack.set(setName, true); - var r:Dynamic = null; - // Children may override it - if (this.topASC != null && this.topASC.findFunction(setName) != null) - { - r = this.topASC.callFunction(setName, [value]); - } - else - { - r = this.callFunction(setName, [value]); - } - this._interp._propTrack.remove(setName); - return r; - } - - case "never" | "null": - return this._interp.error(EInvalidPropSet(name)); + this._interp._propTrack.set(setName, true); + var r:Dynamic = null; + // Children may override it + if (this.topASC != null && this.topASC.findFunction(setName) != null) + { + r = this.topASC.callFunction(setName, [value]); + } + else + { + r = this.callFunction(setName, [value]); + } + this._interp._propTrack.remove(setName); + return r; } - this._interp.variables.set(name, value); - return value; - } - else if (this.superClass != null && Std.isOfType(this.superClass, PolymodScriptClass)) - { - var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass); - try - { - return superScriptClass.fieldWrite(name, value); - } - catch (e:Dynamic) {} - } - else - { - // Class object - if (setClassObjectField(this.superClass, name, value)) - { - return value; - } + case "never" | "null": + return this._interp.error(EInvalidPropSet(name)); + } - @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'"; - } + this._interp.variables.set(name, value); + return value; + } + else if (this.superClass != null && Std.isOfType(this.superClass, PolymodScriptClass)) + { + var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass); + try + { + return superScriptClass.fieldWrite(name, value); + } + catch (e:Dynamic) {} + } + else + { + // Class object + if (setClassObjectField(this.superClass, name, value)) + { + return value; + } + + @:privateAccess this._interp.error(EInvalidAccess(name)); } if (this.superClass == null) { @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "'"; } else { @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'"; } return null; } - private static function retrieveClassObjectFields(o:Dynamic):Array + static function retrieveClassObjectFields(o:Dynamic):Array { final superClassCls = Type.getClass(o); if (superClassCls == null) throw "Provided object isn't a class"; From 11e759c3355cf7385ca42674bb954ee8c0037163 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jun 2026 14:42:15 -0400 Subject: [PATCH 02/12] Disable some annoying checkstyle rules (for now...) --- checkstyle.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/checkstyle.json b/checkstyle.json index 4c2510ee..45c27f8b 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -172,7 +172,9 @@ "ignoreOverride": true, "tokens": ["ABSTRACT_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF"], "modifier": "PUBLIC", - "excludeNames": ["new", "toString"] + "excludeNames": ["new", "toString"], + + "severity": "IGNORE" }, "type": "FieldDocComment" }, @@ -494,7 +496,9 @@ "props": { "enforceReturnTypeForAnonymous": false, "allowEmptyReturn": true, - "enforceReturnType": true + "enforceReturnType": true, + + "severity": "IGNORE" }, "type": "Return" }, From 28bbca43d992729003e221878a70cf253a684273 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jun 2026 14:43:14 -0400 Subject: [PATCH 03/12] Improve handling of properties with `null` or `never` setters (Haxe only lets you access them locally!) --- polymod/hscript/_internal/Interp.hx | 86 ++++++++++++++++------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/polymod/hscript/_internal/Interp.hx b/polymod/hscript/_internal/Interp.hx index 21d1fc5e..8e8a48e9 100644 --- a/polymod/hscript/_internal/Interp.hx +++ b/polymod/hscript/_internal/Interp.hx @@ -759,6 +759,7 @@ class Interp switch (decl?.set) { case "set": + // Allow assigning to "null" only for local fields. final setName = 'set_$id'; if (!_propTrack.exists(setName)) { @@ -771,6 +772,11 @@ class Interp case "never": error(EInvalidPropSet(id)); return null; + + case "null": + // If the property setter is "null", it can only be assigned on local fields. + // Thankfully, this is a local field! + // So we can just fallthrough to the default case. } if ((decl?.isfinal ?? false) && decl?.expr != null) @@ -2386,14 +2392,21 @@ class Interp } catch (e:Dynamic) { - error(EInvalidFinalSet(f)); + error(EInvalidAccess(f)); } } else if (Std.isOfType(o, PolymodStaticClassReference)) { var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); - return ref.setField(f, v); + try + { + return ref.setField(f, v); + } + catch (e:Dynamic) + { + error(EInvalidAccess(f)); + } } else if (Std.isOfType(o, PolymodScriptClass)) { @@ -2891,45 +2904,44 @@ class Interp var fieldDecl = getScriptClassStaticFieldDecl(clsName, fieldName); if (fieldDecl != null) { - if (!this.variables.exists(prefixedName)) + switch (fieldDecl.kind) { - switch (fieldDecl.kind) - { - case KFunction(_fn): - throw 'Cannot override function ${prefixedName}'; - case KVar(v): - if (v.set != null) + case KFunction(_fn): + throw 'Cannot override function ${prefixedName}'; + case KVar(v): + if (v.set != null) + { + switch (v.set) { - switch (v.set) - { - case 'set': - var setterFunc = 'set_${fieldName}'; - if (hasScriptClassStaticFunction(clsName, setterFunc)) - { - return callScriptClassStaticFunction(clsName, setterFunc, [value]); - } - else - { - throw 'Could not resolve setter for property ${prefixedName}'; - } - case 'default': - this.variables.set(prefixedName, value); - return value; - default: + case 'set': + var setterFunc = 'set_${fieldName}'; + if (hasScriptClassStaticFunction(clsName, setterFunc)) + { + return callScriptClassStaticFunction(clsName, setterFunc, [value]); + } + else + { throw 'Could not resolve setter for property ${prefixedName}'; - } - } - else - { - this.variables.set(prefixedName, value); - return value; + } + + case 'never': + throw 'Cannot assign to property ${prefixedName}'; + + case 'null': + throw 'Cannot assign to property ${prefixedName}'; + + case 'default': + this.variables.set(prefixedName, value); + return value; + default: + throw 'Could not resolve setter for property ${prefixedName}'; } - } - } - else - { - this.variables.set(prefixedName, value); - return value; + } + else + { + this.variables.set(prefixedName, value); + return value; + } } } else From c1dbcbdc16c15fcf73299cdeb89dbd8857faef88 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jun 2026 15:00:16 -0400 Subject: [PATCH 04/12] Enable `locateAllFinals()` macro --- include.xml | 4 +--- polymod/hscript/_internal/PolymodFinalMacro.hx | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include.xml b/include.xml index 16e21ce5..3833d7dd 100644 --- a/include.xml +++ b/include.xml @@ -4,7 +4,5 @@ - + diff --git a/polymod/hscript/_internal/PolymodFinalMacro.hx b/polymod/hscript/_internal/PolymodFinalMacro.hx index dd7b6a00..50fe604d 100644 --- a/polymod/hscript/_internal/PolymodFinalMacro.hx +++ b/polymod/hscript/_internal/PolymodFinalMacro.hx @@ -11,9 +11,9 @@ class PolymodFinalMacro public static function getAllFinals():Map> { - // if (_allFinals == null) - // _allFinals = PolymodFinalMacro.fetchAllFinals(); - // return _allFinals; + if (_allFinals == null) + _allFinals = PolymodFinalMacro.fetchAllFinals(); + return _allFinals; return []; } From ba2cdeec9344ca3d1a585353d9313f071b0ede96 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jun 2026 18:27:12 -0400 Subject: [PATCH 05/12] Further restricting final and null/never access --- polymod/hscript/_internal/Interp.hx | 45 +++++++--- .../_internal/PolymodAbstractScriptClass.hx | 57 ++++++++---- .../hscript/_internal/PolymodFinalMacro.hx | 90 +++++++++++++++++-- .../hscript/_internal/PolymodScriptClass.hx | 62 +++++++------ .../PolymodStaticAbstractReference.hx | 10 +++ 5 files changed, 198 insertions(+), 66 deletions(-) diff --git a/polymod/hscript/_internal/Interp.hx b/polymod/hscript/_internal/Interp.hx index 8e8a48e9..0ad6e8d0 100644 --- a/polymod/hscript/_internal/Interp.hx +++ b/polymod/hscript/_internal/Interp.hx @@ -674,10 +674,10 @@ class Interp { var superClass:PolymodAbstractScriptClass = cast(_proxy.superClass, PolymodScriptClass); return superClass.fieldWrite(id, v); + } else { + set(_proxy.superClass, id, v); + return v; } - - Reflect.setProperty(_proxy.superClass, id, v); - return v; } // Fallback to setting in local scope. @@ -745,10 +745,10 @@ class Interp { var superClass:PolymodAbstractScriptClass = cast(_proxy.superClass, PolymodScriptClass); return superClass.fieldWrite(id, v); + } else { + set(_proxy.superClass, id, v); + return v; } - - Reflect.setProperty(_proxy.superClass, id, v); - return v; } @:privateAccess @@ -808,7 +808,7 @@ class Interp return superClass.fieldWrite(id, v); } - Reflect.setProperty(_proxy.superClass, id, v); + set(_proxy.superClass, id, v); return v; } } @@ -822,13 +822,21 @@ class Interp for (imp in _proxy._c.imports) { if (imp.name != id0) continue; - var finals = PolymodFinalMacro.getAllFinals().get(imp.fullPath) ?? []; + var finals = PolymodFinalMacro.getFinals(imp.fullPath); if (finals.contains(id)) { error(EInvalidFinalSet(id)); return null; } + + var privates = PolymodFinalMacro.getPrivateProperties(imp.fullPath); + + if (privates.contains(id)) + { + error(EInvalidPropSet(id)); + return null; + } } } } @@ -1118,7 +1126,7 @@ class Interp } } - inline function error(e:#if hscriptPos ErrorDef #else Error #end, rethrow = false):Dynamic + public inline function error(e:#if hscriptPos ErrorDef #else Error #end, rethrow = false):Dynamic { #if hscriptPos var e = new Error(e, curExpr?.pmin ?? 0, curExpr?.pmax ?? 0, curExpr?.origin ?? 'unknown', curExpr?.line ?? 0); #end if (rethrow) this.rethrow(e) @@ -2423,7 +2431,7 @@ class Interp return superClass.fieldWrite(f, v); } - Reflect.setProperty(proxy.superClass, f, v); + set(proxy.superClass, f, v); } else { @@ -2441,6 +2449,20 @@ class Interp error(EInvalidScriptedVarSet(f)); } + // Prevent setting final variables, or properties with (never) accessors + var finals = PolymodFinalMacro.getFinalsOf(o); + if (finals.contains(f)) + { + error(EInvalidFinalSet(f)); + } + + // Prevent setting properties with (null) accessors + var privates = PolymodFinalMacro.getPrivatePropertiesOf(o); + if (privates.contains(f)) + { + error(EInvalidPropSet(f)); + } + try { Reflect.setProperty(o, f, v); @@ -2909,6 +2931,9 @@ class Interp case KFunction(_fn): throw 'Cannot override function ${prefixedName}'; case KVar(v): + if (v.isfinal) { + throw 'Cannot override final static field ${prefixedName}'; + } if (v.set != null) { switch (v.set) diff --git a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx index ef39574a..87afb25f 100644 --- a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx +++ b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx @@ -1,6 +1,9 @@ package polymod.hscript._internal; import haxe.ds.ObjectMap; +import polymod.util.Util; + +using StringTools; @:forward @:access(polymod.hscript._internal.PolymodScriptClass) @@ -78,7 +81,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass } else if (this.superClass == null) { - @:privateAccess this._interp.error(EInvalidAccess(name)); + return this._interp.error(EInvalidAccess(name)); } else if (Type.getClass(this.superClass) == null) { @@ -89,7 +92,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass } else { - @:privateAccess this._interp.error(EInvalidAccess(name)); + return this._interp.error(EInvalidAccess(name)); } } else if (Std.isOfType(this.superClass, PolymodScriptClass)) @@ -111,7 +114,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass } catch (e:String) { - @:privateAccess this._interp.error(EInvalidAccess(name)); + return this._interp.error(EInvalidAccess(name)); } } } @@ -173,7 +176,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass var decl = this.findVar(name, true); if (decl.isfinal && decl.expr != null) // The variable already exists and has a set value. { - @:privateAccess this._interp.error(EInvalidFinalSet(name)); + return this._interp.error(EInvalidFinalSet(name)); } @:privateAccess @@ -217,22 +220,32 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass else { // Class object - if (setClassObjectField(this.superClass, name, value)) - { - return value; + try { + if (setClassObjectField(this.superClass, name, value)) + { + return value; + } + } catch (e:String) { + if (e.contains('final')) { + return this._interp.error(EInvalidFinalSet(name)); + } else if (e.contains('private')) { + return this._interp.error(EInvalidPropSet(name)); + } else { + return this._interp.error(EInvalidAccess(name)); + } } - - @:privateAccess this._interp.error(EInvalidAccess(name)); } if (this.superClass == null) { - @:privateAccess this._interp.error(EInvalidAccess(name)); + return this._interp.error(EInvalidAccess(name)); } else { - @:privateAccess this._interp.error(EInvalidAccess(name)); + return this._interp.error(EInvalidAccess(name)); } + + trace('fieldWrite() fallthrough???'); return null; } @@ -251,7 +264,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass return fields; } - private static function getClassObjectField(o:Dynamic, field:String):Null + static function getClassObjectField(o:Dynamic, field:String):Null { var fields = retrieveClassObjectFields(o); if (fields.contains(field) || fields.contains('get_$field')) return Reflect.getProperty(o, field); @@ -259,18 +272,32 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass throw 'No such field $field'; } - private static function setClassObjectField(o:Dynamic, field:String, value:Dynamic):Bool + static function setClassObjectField(o:Dynamic, field:String, value:Dynamic):Bool { + var finals = PolymodFinalMacro.getFinalsOf(o); + if (finals.contains(field)) + { + throw 'Cannot set final field $field'; + } + + var privates = PolymodFinalMacro.getPrivatePropertiesOf(o); + if (privates.contains(field)) + { + throw 'Cannot set private field $field'; + } + var fields = retrieveClassObjectFields(o); if (fields.contains(field) || fields.contains('set_$field')) { + trace('Setting ${Util.getTypeNameOf(o)}.${field} = ${value}'); Reflect.setProperty(o, field, value); return true; } - return false; + + throw 'No such field $field'; } - private static function hasClassObjectField(o:Dynamic, field:String):Bool + static function hasClassObjectField(o:Dynamic, field:String):Bool { var fields = retrieveClassObjectFields(o); if (fields.contains(field) || fields.contains('get_$field') || fields.contains('set_$field')) return true; diff --git a/polymod/hscript/_internal/PolymodFinalMacro.hx b/polymod/hscript/_internal/PolymodFinalMacro.hx index 50fe604d..c7789a4e 100644 --- a/polymod/hscript/_internal/PolymodFinalMacro.hx +++ b/polymod/hscript/_internal/PolymodFinalMacro.hx @@ -1,20 +1,44 @@ package polymod.hscript._internal; +import polymod.util.Util; import haxe.macro.Context; import haxe.macro.Expr; import haxe.macro.Type; import haxe.rtti.Meta; +@:nullSafety class PolymodFinalMacro { - private static var _allFinals:Map> = null; + public static inline function getFinals(fullPath:String):Array { + return getAllFinals().get(fullPath) ?? []; + } + + public static inline function getFinalsOf(obj:Dynamic):Array { + return getFinals(Util.getTypeNameOf(obj)); + } + + public static inline function getPrivateProperties(fullPath:String):Array { + return getAllPrivateProperties().get(fullPath) ?? []; + } + + public static inline function getPrivatePropertiesOf(obj:Dynamic):Array { + return getFinals(Util.getTypeNameOf(obj)); + } + + private static var _allFinals:Null>> = null; public static function getAllFinals():Map> { - if (_allFinals == null) - _allFinals = PolymodFinalMacro.fetchAllFinals(); + if (_allFinals == null) _allFinals = PolymodFinalMacro.fetchAllFinals(); return _allFinals; - return []; + } + + private static var _allPrivates:Null>> = null; + + public static function getAllPrivateProperties():Map> + { + if (_allPrivates == null) _allPrivates = PolymodFinalMacro.fetchAllPrivateProperties(); + return _allPrivates; } public static macro function locateAllFinals():Void @@ -23,6 +47,7 @@ class PolymodFinalMacro if (calledBefore) return; var allFinals:Array = []; + var allPrivates:Array = []; for (type in types) { @@ -34,17 +59,36 @@ class PolymodFinalMacro if (classType.isInterface) continue; var finals:Array = []; + var privates:Array = []; for (field in classType.statics.get()) { - if (!field.isFinal) continue; - finals.push(field.name); + // Add final variables. + if (field.isFinal) finals.push(field.name); + + // Add properties with `never`/`null` accessors. + switch (field.kind) { + case FVar(read, write): + switch (write) { + case AccNever: + finals.push(field.name); + case AccNo: + privates.push(field.name); + default: // Do nothing + } + default: // Do nothing + } } - if (finals.length == 0) continue; + if (finals.length > 0) { + var entryData = [macro $v{classPath}, macro $v{finals}]; + allFinals.push(macro $a{entryData}); + } - var entryData = [macro $v{classPath}, macro $v{finals}]; + if (privates.length > 0) { + var entryData = [macro $v{classPath}, macro $v{privates}]; + allPrivates.push(macro $a{entryData}); + } - allFinals.push(macro $a{entryData}); default: continue; } @@ -57,7 +101,9 @@ class PolymodFinalMacro case TInst(t, _): var finalMacroClassType:ClassType = t.get(); finalMacroClassType.meta.remove('finals'); + finalMacroClassType.meta.remove('privates'); finalMacroClassType.meta.add('finals', allFinals, Context.currentPos()); + finalMacroClassType.meta.add('privates', allPrivates, Context.currentPos()); default: throw 'Could not find PolymodFinalMacro type'; } @@ -95,4 +141,30 @@ class PolymodFinalMacro throw 'No finals found in PolymodFinalMacro'; } } + + public static function fetchAllPrivateProperties():Map> + { + var metaData = Meta.getType(PolymodFinalMacro); + + if (metaData.privates != null) + { + var result:Map> = []; + + for (element in metaData.privates) + { + if (element.length != 2) throw 'Malformed element in privates: ' + element; + + var classPath:String = element[0]; + var privates:Array = element[1]; + + result.set(classPath, privates); + } + + return result; + } + else + { + throw 'No private properties found in PolymodFinalMacro'; + } + } } diff --git a/polymod/hscript/_internal/PolymodScriptClass.hx b/polymod/hscript/_internal/PolymodScriptClass.hx index 8b559706..93c8f52f 100644 --- a/polymod/hscript/_internal/PolymodScriptClass.hx +++ b/polymod/hscript/_internal/PolymodScriptClass.hx @@ -150,35 +150,33 @@ class PolymodScriptClass */ static function registerScriptClassByPath(path:String):Void { - @:privateAccess { - var scriptBody = Polymod.assetLibrary.getText(path); - if (scriptBody == null) - { - Polymod.error(SCRIPT_PARSE_FAILED, 'Error while loading script "${path}", could not retrieve script contents!', SCRIPT_RUNTIME); - return; - } - try - { - registerScriptClassByString(scriptBody, path); - } - catch (err:Expr.Error) + var scriptBody = Polymod.assetLibrary.getText(path); + if (scriptBody == null) + { + Polymod.error(SCRIPT_PARSE_FAILED, 'Error while loading script "${path}", could not retrieve script contents!', SCRIPT_RUNTIME); + return; + } + try + { + registerScriptClassByString(scriptBody, path); + } + catch (err:Expr.Error) + { + var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; + #if hscriptPos + switch (err.e) + #else + switch (err) + #end { - var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; - #if hscriptPos - switch (err.e) - #else - switch (err) - #end - { - case EUnexpected(s): - Polymod.error(SCRIPT_PARSE_FAILED, - 'Error while parsing function ${path}#${errLine}: EUnexpected' + '\n' + 'Unexpected token "${s}", is there invalid syntax on this line?', SCRIPT_RUNTIME); - case EClassUnresolvedSuperclass(cls, reason): - Polymod.error(SCRIPT_PARSE_FAILED, - 'Error while parsing class ${path}#${errLine}: EClassUnresolvedSuperclass' + '\n' + 'Unresolved superclass "${cls}", ${reason}', SCRIPT_RUNTIME); - default: - Polymod.error(SCRIPT_PARSE_FAILED, 'Error while parsing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}', SCRIPT_RUNTIME); - } + case EUnexpected(s): + Polymod.error(SCRIPT_PARSE_FAILED, + 'Error while parsing function ${path}#${errLine}: EUnexpected' + '\n' + 'Unexpected token "${s}", is there invalid syntax on this line?', SCRIPT_RUNTIME); + case EClassUnresolvedSuperclass(cls, reason): + Polymod.error(SCRIPT_PARSE_FAILED, + 'Error while parsing class ${path}#${errLine}: EClassUnresolvedSuperclass' + '\n' + 'Unresolved superclass "${cls}", ${reason}', SCRIPT_RUNTIME); + default: + Polymod.error(SCRIPT_PARSE_FAILED, 'Error while parsing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}', SCRIPT_RUNTIME); } } } @@ -454,7 +452,7 @@ class PolymodScriptClass callFunction("new", args); if (superClass == null && _c.extend != null) { - @:privateAccess _interp.error(EClassSuperNotCalled); + _interp.error(EClassSuperNotCalled); } } else if (_c.extend != null) @@ -533,7 +531,7 @@ class PolymodScriptClass if (clsToCreate == null) { - @:privateAccess _interp.error(EClassUnresolvedSuperclass(fullExtendString, 'WHY?')); + _interp.error(EClassUnresolvedSuperclass(fullExtendString, 'WHY?')); } } else if (_c.imports.exists(extendString)) @@ -542,12 +540,12 @@ class PolymodScriptClass if (clsToCreate == null) { - @:privateAccess _interp.error(EClassUnresolvedSuperclass(extendString, 'target class blacklisted')); + _interp.error(EClassUnresolvedSuperclass(extendString, 'target class blacklisted')); } } else { - @:privateAccess _interp.error(EClassUnresolvedSuperclass(extendString, 'missing import')); + _interp.error(EClassUnresolvedSuperclass(extendString, 'missing import')); } superClass = Type.createInstance(clsToCreate, args); diff --git a/polymod/hscript/_internal/PolymodStaticAbstractReference.hx b/polymod/hscript/_internal/PolymodStaticAbstractReference.hx index 657ce288..ecf1ac19 100644 --- a/polymod/hscript/_internal/PolymodStaticAbstractReference.hx +++ b/polymod/hscript/_internal/PolymodStaticAbstractReference.hx @@ -105,6 +105,16 @@ class PolymodStaticAbstractReference { if (this.absImpl != null) { + var finals = PolymodFinalMacro.getFinalsOf(this.absImpl); + if (finals.contains(fieldName)) { + throw 'Could not assign abstract static final field ${fieldName}'; + } + + var privates = PolymodFinalMacro.getPrivatePropertiesOf(this.absImpl); + if (privates.contains(fieldName)) { + throw 'Could not assign abstract static property ${fieldName}'; + } + if (Reflect.hasField(this.absImpl, fieldName)) { Reflect.setProperty(this.absImpl, fieldName, fieldValue); From a4843fcac62dbbb95492848df8303b065267f958 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 11 Jun 2026 21:38:54 -0400 Subject: [PATCH 06/12] Fixed constructor assignment not working 100% as intended. --- polymod/hscript/_internal/Interp.hx | 37 ++----------------- .../_internal/PolymodAbstractScriptClass.hx | 23 +++++++++++- .../PolymodStaticAbstractReference.hx | 12 +----- 3 files changed, 26 insertions(+), 46 deletions(-) diff --git a/polymod/hscript/_internal/Interp.hx b/polymod/hscript/_internal/Interp.hx index 0ad6e8d0..1b917afc 100644 --- a/polymod/hscript/_internal/Interp.hx +++ b/polymod/hscript/_internal/Interp.hx @@ -808,7 +808,9 @@ class Interp return superClass.fieldWrite(id, v); } - set(_proxy.superClass, id, v); + // Directly assign the value. + // This is needed because `assignValue` may sometimes be called from the constructor. + PolymodAbstractScriptClass.setClassObjectField(_proxy.superClass, id, v); return v; } } @@ -2346,23 +2348,6 @@ class Interp } } #end - // if (o == null) error(EInvalidAccess(f)); - // return - // { - // #if php - // // https://github.com/HaxeFoundation/haxe/issues/4915 - // try - // { - // Reflect.getProperty(o, f); - // } - // catch (e:Dynamic) - // { - // Reflect.field(o, f); - // } - // #else - // Reflect.getProperty(o, f); - // #end - // } } function set(o:Dynamic, f:String, v:Dynamic):Dynamic @@ -2449,23 +2434,9 @@ class Interp error(EInvalidScriptedVarSet(f)); } - // Prevent setting final variables, or properties with (never) accessors - var finals = PolymodFinalMacro.getFinalsOf(o); - if (finals.contains(f)) - { - error(EInvalidFinalSet(f)); - } - - // Prevent setting properties with (null) accessors - var privates = PolymodFinalMacro.getPrivatePropertiesOf(o); - if (privates.contains(f)) - { - error(EInvalidPropSet(f)); - } - try { - Reflect.setProperty(o, f, v); + PolymodAbstractScriptClass.setClassObjectField(o, f, v); } catch (e) { diff --git a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx index 87afb25f..532f6c83 100644 --- a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx +++ b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx @@ -251,6 +251,8 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass static function retrieveClassObjectFields(o:Dynamic):Array { + if (Util.getTypeNameOf(o) == "Object") return []; + final superClassCls = Type.getClass(o); if (superClassCls == null) throw "Provided object isn't a class"; @@ -266,14 +268,30 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass static function getClassObjectField(o:Dynamic, field:String):Null { + if (Util.getTypeNameOf(o) == "Object") return Reflect.getProperty(o, field); + var fields = retrieveClassObjectFields(o); if (fields.contains(field) || fields.contains('get_$field')) return Reflect.getProperty(o, field); throw 'No such field $field'; } - static function setClassObjectField(o:Dynamic, field:String, value:Dynamic):Bool + /** + * Assign a field of a class object. + * Checks for `final` and `private` variables and fails to assign to immutable fields. + * + * @param o The object to modify. + * @param field The name of the field to modify. + * @param value The value to assign to the field. + * @return `true` for success. + */ + public static function setClassObjectField(o:Dynamic, field:String, value:Dynamic):Bool { + if (Util.getTypeNameOf(o) == "Object") { + Reflect.setProperty(o, field, value); + return true; + } + var finals = PolymodFinalMacro.getFinalsOf(o); if (finals.contains(field)) { @@ -289,7 +307,6 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass var fields = retrieveClassObjectFields(o); if (fields.contains(field) || fields.contains('set_$field')) { - trace('Setting ${Util.getTypeNameOf(o)}.${field} = ${value}'); Reflect.setProperty(o, field, value); return true; } @@ -299,6 +316,8 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass static function hasClassObjectField(o:Dynamic, field:String):Bool { + if (Util.getTypeNameOf(o) == "Object") return Reflect.hasField(o, field); + var fields = retrieveClassObjectFields(o); if (fields.contains(field) || fields.contains('get_$field') || fields.contains('set_$field')) return true; diff --git a/polymod/hscript/_internal/PolymodStaticAbstractReference.hx b/polymod/hscript/_internal/PolymodStaticAbstractReference.hx index ecf1ac19..97406bbc 100644 --- a/polymod/hscript/_internal/PolymodStaticAbstractReference.hx +++ b/polymod/hscript/_internal/PolymodStaticAbstractReference.hx @@ -105,19 +105,9 @@ class PolymodStaticAbstractReference { if (this.absImpl != null) { - var finals = PolymodFinalMacro.getFinalsOf(this.absImpl); - if (finals.contains(fieldName)) { - throw 'Could not assign abstract static final field ${fieldName}'; - } - - var privates = PolymodFinalMacro.getPrivatePropertiesOf(this.absImpl); - if (privates.contains(fieldName)) { - throw 'Could not assign abstract static property ${fieldName}'; - } - if (Reflect.hasField(this.absImpl, fieldName)) { - Reflect.setProperty(this.absImpl, fieldName, fieldValue); + PolymodAbstractScriptClass.setClassObjectField(this.absImpl, fieldName, fieldValue); return fieldValue; } var setterName:String = 'set_$fieldName'; From a15e39e008213b0d019b54878c117718239aea56 Mon Sep 17 00:00:00 2001 From: Hyper_ <40342021+NotHyper-474@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:17:28 -0300 Subject: [PATCH 07/12] Fix unfenced import causing compile errors --- polymod/hscript/_internal/PolymodFinalMacro.hx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/polymod/hscript/_internal/PolymodFinalMacro.hx b/polymod/hscript/_internal/PolymodFinalMacro.hx index c7789a4e..10893a9b 100644 --- a/polymod/hscript/_internal/PolymodFinalMacro.hx +++ b/polymod/hscript/_internal/PolymodFinalMacro.hx @@ -1,14 +1,18 @@ package polymod.hscript._internal; -import polymod.util.Util; +#if macro import haxe.macro.Context; import haxe.macro.Expr; import haxe.macro.Type; +#else +import polymod.util.Util; +#end import haxe.rtti.Meta; @:nullSafety class PolymodFinalMacro { + #if !macro public static inline function getFinals(fullPath:String):Array { return getAllFinals().get(fullPath) ?? []; } @@ -40,6 +44,7 @@ class PolymodFinalMacro if (_allPrivates == null) _allPrivates = PolymodFinalMacro.fetchAllPrivateProperties(); return _allPrivates; } + #end public static macro function locateAllFinals():Void { From 011745d90990c55e847071c49557a1321a83f699 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 12 Jun 2026 02:23:55 -0400 Subject: [PATCH 08/12] Missed a spot for the constructor fix. --- polymod/hscript/_internal/Interp.hx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/polymod/hscript/_internal/Interp.hx b/polymod/hscript/_internal/Interp.hx index 1b917afc..55c0c051 100644 --- a/polymod/hscript/_internal/Interp.hx +++ b/polymod/hscript/_internal/Interp.hx @@ -745,10 +745,12 @@ class Interp { var superClass:PolymodAbstractScriptClass = cast(_proxy.superClass, PolymodScriptClass); return superClass.fieldWrite(id, v); - } else { - set(_proxy.superClass, id, v); - return v; } + + // Directly assign the value. + // This is needed because `assignValue` may sometimes be called from the constructor. + PolymodAbstractScriptClass.setClassObjectField(_proxy.superClass, id, v); + return v; } @:privateAccess From a5260e88c70244af44f0f48ef6c6ea36d36c5478 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 13 Jun 2026 03:02:55 -0400 Subject: [PATCH 09/12] Remove some unused fields --- polymod/hscript/_internal/PolymodScriptClassMacro.hx | 2 -- 1 file changed, 2 deletions(-) diff --git a/polymod/hscript/_internal/PolymodScriptClassMacro.hx b/polymod/hscript/_internal/PolymodScriptClassMacro.hx index 2437f016..7d4e6cb0 100644 --- a/polymod/hscript/_internal/PolymodScriptClassMacro.hx +++ b/polymod/hscript/_internal/PolymodScriptClassMacro.hx @@ -115,7 +115,6 @@ class PolymodScriptClassMacro } case TType(t, _params): - var typedefType:DefType = t.get(); var typedefPath:String = t.toString(); var typedefTarget:Type = Context.followWithAbstracts(type); @@ -241,7 +240,6 @@ class PolymodScriptClassMacro } else { - var abstractImplPath = abstractType.impl.toString(); var abstractImplType = abstractType.impl.get(); var abstractImplStatics:Array = abstractImplType.statics.get(); From d19b7aa329392f17c3b06524a8edc5743c82eb56 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 13 Jun 2026 03:03:59 -0400 Subject: [PATCH 10/12] Add the stack trace for uncaught exceptions in script constructors. --- .../hscript/_internal/HScriptedClassMacro.hx | 7 +++-- polymod/hscript/_internal/Interp.hx | 6 ++++- polymod/util/Util.hx | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/polymod/hscript/_internal/HScriptedClassMacro.hx b/polymod/hscript/_internal/HScriptedClassMacro.hx index c6b87522..b3d612bb 100644 --- a/polymod/hscript/_internal/HScriptedClassMacro.hx +++ b/polymod/hscript/_internal/HScriptedClassMacro.hx @@ -185,8 +185,11 @@ class HScriptedClassMacro } catch (error) { + var callStack:String = polymod.util.Util.fetchCallStack(); + polymod.Polymod.error(SCRIPT_RUNTIME_EXCEPTION, - 'Could not construct instance of scripted class (${clsName} extends ' + $v{clsTypeName} + '):\n${error}', SCRIPT_RUNTIME); + 'An uncaught exception was thrown while constructing an instance of scripted class (${clsName} extends ' + $v{clsTypeName} + '):\n$error\n$callStack', + SCRIPT_RUNTIME); return null; } }, @@ -524,7 +527,7 @@ class HScriptedClassMacro { if (_asc == null) { - return $v{'PolymodScriptedClass<${cls.name} extends ${cls.superClass.t.get().name}>(NO ASC)'}; + return $v{'PolymodScriptClass<${cls.name} extends ${cls.superClass.t.get().name}>(NO ASC)'}; } else { diff --git a/polymod/hscript/_internal/Interp.hx b/polymod/hscript/_internal/Interp.hx index 55c0c051..e37c0bec 100644 --- a/polymod/hscript/_internal/Interp.hx +++ b/polymod/hscript/_internal/Interp.hx @@ -424,7 +424,11 @@ class Interp } catch (error) { - Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not construct instance of scripted class ($clsToInit extends ' + clsName + '):\n${error}'); + var callStack:String = polymod.util.Util.fetchCallStack(); + + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, + 'An uncaught exception was thrown while constructing an instance of scripted class ($clsToInit extends ' + clsName + '):\n$error\n$callStack', + SCRIPT_RUNTIME); return null; } } diff --git a/polymod/util/Util.hx b/polymod/util/Util.hx index 6a265873..55ed0f97 100644 --- a/polymod/util/Util.hx +++ b/polymod/util/Util.hx @@ -694,6 +694,32 @@ class Util return indexOfInsens(arr, x, ignoreConfig) != -1; } + public static function fetchCallStack(exception:Bool = true):String { + var errorMessage:String = ""; + var callStack:Array = haxe.CallStack.exceptionStack(true); + + for (stackItem in callStack) + { + switch (stackItem) + { + case FilePos(innerStackItem, file, line, column): + errorMessage += ' in ${file}#${line}'; + if (column != null) errorMessage += ':${column}'; + case CFunction: + errorMessage += '[Function] '; + case Module(m): + errorMessage += '[Module(${m})] '; + case Method(classname, method): + errorMessage += '[Function(${classname}.${method})] '; + case LocalFunction(v): + errorMessage += '[LocalFunction(${v})] '; + } + errorMessage += '\n'; + } + + return errorMessage; + } + public static function getTypeNameOf(obj:Dynamic):String { var type = Type.typeof(obj); From 2e6063056fe93c0cb40bf98e65632ceb2c62fe49 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 13 Jun 2026 03:04:58 -0400 Subject: [PATCH 11/12] scriptInit now returns Null for when the constructor fails. --- polymod/hscript/_internal/HScriptedClassMacro.hx | 4 +++- polymod/util/MacroUtil.hx | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/polymod/hscript/_internal/HScriptedClassMacro.hx b/polymod/hscript/_internal/HScriptedClassMacro.hx index b3d612bb..8c86d10a 100644 --- a/polymod/hscript/_internal/HScriptedClassMacro.hx +++ b/polymod/hscript/_internal/HScriptedClassMacro.hx @@ -145,6 +145,8 @@ class HScriptedClassMacro { // Context.info(' Building scripted class init() function', Context.currentPos()); var clsTypeName:String = cls.pack.join('.') != '' ? '${cls.pack.join('.')}.${cls.name}' : cls.name; + // scriptInit returns null if the script throws an error while initializing. + var clsType:ComplexType = Context.toComplexType(Context.getType(clsTypeName)); var function_init:Field = { name: 'scriptInit', @@ -157,7 +159,7 @@ class HScriptedClassMacro args: [ {name: 'clsName', type: macro :String}, {name: 'args', type: macro :...Dynamic}], params: null, - ret: Context.toComplexType(Context.getType(clsTypeName)), + ret: polymod.util.MacroUtil.nullable(clsType), expr: macro { var clsRef = polymod.hscript._internal.PolymodStaticClassReference.tryBuild(clsName); diff --git a/polymod/util/MacroUtil.hx b/polymod/util/MacroUtil.hx index 68c1ebc5..84a4943c 100644 --- a/polymod/util/MacroUtil.hx +++ b/polymod/util/MacroUtil.hx @@ -39,6 +39,10 @@ class MacroUtil } } + public static function nullable(complexType : ComplexType):ComplexType { + return macro: Null<$complexType>; + } + public static function areClassesEqual(class1:ClassType, class2:ClassType):Bool { return class1.pack.join('.') == class2.pack.join('.') && class1.name == class2.name; From 23fe9e240129103febe3d5dc8a224086e20fb553 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 13 Jun 2026 03:08:41 -0400 Subject: [PATCH 12/12] Write the list of finals/private properties to a resource file to prevent a crash. --- .../hscript/_internal/PolymodFinalMacro.hx | 185 +++++++++++++----- 1 file changed, 135 insertions(+), 50 deletions(-) diff --git a/polymod/hscript/_internal/PolymodFinalMacro.hx b/polymod/hscript/_internal/PolymodFinalMacro.hx index 10893a9b..dc0f8a31 100644 --- a/polymod/hscript/_internal/PolymodFinalMacro.hx +++ b/polymod/hscript/_internal/PolymodFinalMacro.hx @@ -5,20 +5,28 @@ import haxe.macro.Context; import haxe.macro.Expr; import haxe.macro.Type; #else -import polymod.util.Util; #end +import polymod.util.Util; import haxe.rtti.Meta; @:nullSafety class PolymodFinalMacro { - #if !macro + /** + * The name for the Haxe resource that stores Generation Metadata. + */ + static inline final METADATA_RESOURCE_NAME:String = 'PolymodFinalMacro_METADATA'; + public static inline function getFinals(fullPath:String):Array { return getAllFinals().get(fullPath) ?? []; } public static inline function getFinalsOf(obj:Dynamic):Array { - return getFinals(Util.getTypeNameOf(obj)); + while (Std.isOfType(obj, PolymodScriptClass)) obj = obj.superClass; + + var typeName:String = Util.getTypeNameOf(obj); + var result = getFinals(typeName); + return result; } public static inline function getPrivateProperties(fullPath:String):Array { @@ -26,7 +34,11 @@ class PolymodFinalMacro } public static inline function getPrivatePropertiesOf(obj:Dynamic):Array { - return getFinals(Util.getTypeNameOf(obj)); + while (Std.isOfType(obj, PolymodScriptClass)) obj = obj.superClass; + + var typeName:String = Util.getTypeNameOf(obj); + var result = getPrivateProperties(typeName); + return result; } private static var _allFinals:Null>> = null; @@ -44,15 +56,16 @@ class PolymodFinalMacro if (_allPrivates == null) _allPrivates = PolymodFinalMacro.fetchAllPrivateProperties(); return _allPrivates; } - #end public static macro function locateAllFinals():Void { Context.onAfterTyping((types) -> { if (calledBefore) return; - var allFinals:Array = []; - var allPrivates:Array = []; + var startTime:Float = Sys.time(); + + var allFinals:Array = []; + var allPrivates:Array = []; for (type in types) { @@ -63,35 +76,16 @@ class PolymodFinalMacro var classPath:String = t.toString(); if (classType.isInterface) continue; - var finals:Array = []; - var privates:Array = []; - for (field in classType.statics.get()) - { - // Add final variables. - if (field.isFinal) finals.push(field.name); - - // Add properties with `never`/`null` accessors. - switch (field.kind) { - case FVar(read, write): - switch (write) { - case AccNever: - finals.push(field.name); - case AccNo: - privates.push(field.name); - default: // Do nothing - } - default: // Do nothing - } - } - + var finals:Array = listFinalsOfClassType(classType); if (finals.length > 0) { - var entryData = [macro $v{classPath}, macro $v{finals}]; - allFinals.push(macro $a{entryData}); + var entryData:Array = [classPath, finals]; + allFinals.push(entryData); } + var privates:Array = listPrivatesOfClassType(classType); if (privates.length > 0) { - var entryData = [macro $v{classPath}, macro $v{privates}]; - allPrivates.push(macro $a{entryData}); + var entryData:Array = [classPath, privates]; + allPrivates.push(entryData); } default: @@ -99,37 +93,117 @@ class PolymodFinalMacro } } - var finalMacroType:Type = Context.getType('polymod.hscript._internal.PolymodFinalMacro'); + var metadataHXSF = haxe.Serializer.run({ + finals: allFinals, + privates: allPrivates + }); + Context.addResource(METADATA_RESOURCE_NAME, haxe.io.Bytes.ofString(metadataHXSF)); - switch (finalMacroType) - { - case TInst(t, _): - var finalMacroClassType:ClassType = t.get(); - finalMacroClassType.meta.remove('finals'); - finalMacroClassType.meta.remove('privates'); - finalMacroClassType.meta.add('finals', allFinals, Context.currentPos()); - finalMacroClassType.meta.add('privates', allPrivates, Context.currentPos()); - default: - throw 'Could not find PolymodFinalMacro type'; - } + var endTime:Float = Sys.time(); + + var duration:Float = endTime - startTime; + + Context.info('PolymodFinalMacro: ' + + 'Detected ${allFinals.length} classes with final variables, ' + + '${allPrivates.length} classes with (default,null) properties ' + + 'in ${duration} sec.', + Context.currentPos()); calledBefore = true; }); } #if macro + static function listFinalsOfClassType(classType:Null):Array { + if (classType == null) return []; + + var result:Array = []; + + for (field in classType.fields.get()) + { + // Add final variables. + if (field.isFinal) result.push(field.name); + + // Add properties with `never` accessors. + switch (field.kind) { + case FVar(read, write): + switch (write) { + case AccNever: + result.push(field.name); + default: // Do nothing + } + default: // Do nothing + } + } + + for (field in classType.statics.get()) + { + // Add final variables. + if (field.isFinal) result.push(field.name); + + // Add properties with `never` accessors. + switch (field.kind) { + case FVar(read, write): + switch (write) { + case AccNever: + result.push(field.name); + default: // Do nothing + } + default: // Do nothing + } + } + + return result.concat(listFinalsOfClassType(classType?.superClass?.t?.get())); + } + + static function listPrivatesOfClassType(classType:Null):Array { + if (classType == null) return []; + + var result:Array = []; + + for (field in classType.fields.get()) { + // Add properties with `null` accessors. + switch (field.kind) { + case FVar(read, write): + switch (write) { + case AccNo: + result.push(field.name); + default: // Do nothing + } + default: // Do nothing + } + } + + for (field in classType.statics.get()) + { + // Add properties with `null` accessors. + switch (field.kind) { + case FVar(read, write): + switch (write) { + case AccNo: + result.push(field.name); + default: // Do nothing + } + default: // Do nothing + } + } + + return result.concat(listPrivatesOfClassType(classType?.superClass?.t?.get())); + } + static var calledBefore:Bool = false; #end public static function fetchAllFinals():Map> { - var metaData = Meta.getType(PolymodFinalMacro); + var metaData = fetchMetadata(); + var finals:Array = cast metaData.finals; - if (metaData.finals != null) + if (finals != null) { var result:Map> = []; - for (element in metaData.finals) + for (element in finals) { if (element.length != 2) throw 'Malformed element in finals: ' + element; @@ -149,13 +223,14 @@ class PolymodFinalMacro public static function fetchAllPrivateProperties():Map> { - var metaData = Meta.getType(PolymodFinalMacro); + var metaData = fetchMetadata(); + var privates:Array = cast metaData.privates; - if (metaData.privates != null) + if (privates != null) { var result:Map> = []; - for (element in metaData.privates) + for (element in privates) { if (element.length != 2) throw 'Malformed element in privates: ' + element; @@ -172,4 +247,14 @@ class PolymodFinalMacro throw 'No private properties found in PolymodFinalMacro'; } } + + static var _metadata:Dynamic = null; + static function fetchMetadata():Dynamic + { + if (_metadata != null) return _metadata; + + var metaDataHXSF:String = haxe.Resource.getString(METADATA_RESOURCE_NAME); + _metadata = haxe.Unserializer.run(metaDataHXSF); + return _metadata; + } }