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"
},
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/HScriptedClassMacro.hx b/polymod/hscript/_internal/HScriptedClassMacro.hx
index c6b87522..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);
@@ -185,8 +187,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 +529,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 a1c45e8e..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;
}
}
@@ -508,7 +512,7 @@ class Interp
}
}
- private static function registerScriptClass(c:ClassDecl)
+ static function registerScriptClass(c:ClassDecl)
{
var name = Util.getFullClassName(c);
@@ -674,10 +678,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.
@@ -747,7 +751,9 @@ class Interp
return superClass.fieldWrite(id, v);
}
- Reflect.setProperty(_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;
}
@@ -759,6 +765,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,11 +778,16 @@ 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)
{
- error(EInvalidAccess(id));
+ error(EInvalidFinalSet(id));
return null;
}
}
@@ -802,7 +814,9 @@ class Interp
return superClass.fieldWrite(id, v);
}
- Reflect.setProperty(_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;
}
}
@@ -816,13 +830,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;
+ }
}
}
}
@@ -976,7 +998,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;
@@ -1112,7 +1134,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)
@@ -1480,7 +1502,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 +2316,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))
@@ -2348,23 +2354,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
@@ -2402,14 +2391,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))
{
@@ -2426,7 +2422,7 @@ class Interp
return superClass.fieldWrite(f, v);
}
- Reflect.setProperty(proxy.superClass, f, v);
+ set(proxy.superClass, f, v);
}
else
{
@@ -2442,14 +2438,11 @@ class Interp
}
error(EInvalidScriptedVarSet(f));
-
- // Reflect.setProperty(o, f, v);
- // return v;
}
try
{
- Reflect.setProperty(o, f, v);
+ PolymodAbstractScriptClass.setClassObjectField(o, f, v);
}
catch (e)
{
@@ -2910,45 +2903,47 @@ 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.isfinal) {
+ throw 'Cannot override final static field ${prefixedName}';
+ }
+ 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
@@ -3011,4 +3006,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..532f6c83 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,8 +81,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}"';
+ return this._interp.error(EInvalidAccess(name));
}
else if (Type.getClass(this.superClass) == null)
{
@@ -90,8 +92,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))}"';
+ return this._interp.error(EInvalidAccess(name));
}
}
else if (Std.isOfType(this.superClass, PolymodScriptClass))
@@ -113,8 +114,7 @@ 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)) + "'";
+ return this._interp.error(EInvalidAccess(name));
}
}
}
@@ -171,84 +171,88 @@ 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.
+ {
+ return 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))
+ case "never" | "null":
+ return this._interp.error(EInvalidPropSet(name));
+ }
+
+ 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
+ try {
+ if (setClassObjectField(this.superClass, name, value))
{
- var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass);
- try
- {
- return superScriptClass.fieldWrite(name, value);
- }
- catch (e:Dynamic) {}
+ return value;
}
- else
- {
- // Class object
- if (setClassObjectField(this.superClass, name, value))
- {
- return value;
- }
-
- @: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)) + "'";
+ } 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));
}
+ }
}
if (this.superClass == null)
{
- @:privateAccess this._interp.error(EInvalidAccess(name));
- // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "'";
+ return this._interp.error(EInvalidAccess(name));
}
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 this._interp.error(EInvalidAccess(name));
}
+
+ trace('fieldWrite() fallthrough???');
return null;
}
- private static function retrieveClassObjectFields(o:Dynamic):Array
+ 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";
@@ -262,27 +266,58 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass
return fields;
}
- private static function getClassObjectField(o:Dynamic, field:String):Null
+ 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';
}
- private 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))
+ {
+ 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'))
{
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
{
+ 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/PolymodFinalMacro.hx b/polymod/hscript/_internal/PolymodFinalMacro.hx
index dd7b6a00..dc0f8a31 100644
--- a/polymod/hscript/_internal/PolymodFinalMacro.hx
+++ b/polymod/hscript/_internal/PolymodFinalMacro.hx
@@ -1,20 +1,60 @@
package polymod.hscript._internal;
+#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
+#else
+#end
+import polymod.util.Util;
import haxe.rtti.Meta;
+@:nullSafety
class PolymodFinalMacro
{
- private static var _allFinals:Map> = null;
+ /**
+ * 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 {
+ 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 {
+ return getAllPrivateProperties().get(fullPath) ?? [];
+ }
+
+ public static inline function getPrivatePropertiesOf(obj:Dynamic):Array {
+ 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