Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0f4c754
Enable processing for all abstracts.
EliteMasterEric Jan 27, 2026
290333a
fix compile errors
lemz1 Jan 30, 2026
fa147e0
Gracefully fail (and display a script error) rather than throwing an …
EliteMasterEric Jan 31, 2026
978a360
break out from loops after finding getter/setter
NotHyper-474 Feb 1, 2026
a4b5479
Remove some extra print calls.
EliteMasterEric Feb 2, 2026
c053b82
Properly support enum abstract values (i.e. inline get-only static fi…
EliteMasterEric Feb 2, 2026
00ed2ef
Add timing data for processing abstracts to the compilation output.
EliteMasterEric Feb 2, 2026
cc0a29a
Refactored abstract class field access to account for impls that can'…
EliteMasterEric Feb 3, 2026
fa31496
Merge remote-tracking branch 'origin/experimental' into experimental-…
EliteMasterEric Feb 3, 2026
26171e0
Resolve an issue preventing abstracts from being instantiated.
EliteMasterEric Feb 3, 2026
3aada1d
Add support for resolving typedefs to their underlying types.
EliteMasterEric Feb 3, 2026
fe8d946
Merge branch 'experimental' into experimental-abstracts
AbnormalPoof Feb 3, 2026
d6df873
Store abstract statics as a Resource to prevent compiler stall
NotHyper-474 Feb 2, 2026
205d572
fix abstract static getter/setter if check
lemz1 Feb 3, 2026
e2fa62b
call actual constructor of abstract
lemz1 Feb 3, 2026
2a0f3df
Fixes to access for static fields of abstracts (getters and setters)
EliteMasterEric Feb 4, 2026
43c605e
Run haxe formatter
AbnormalPoof Feb 4, 2026
e5b7d8a
fix compiler errors when hscriptPos is not defined
lemz1 Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions polymod/hscript/_internal/Parser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -1800,8 +1800,10 @@ class Parser
input = oldInput;
readPos = oldPos;
offset = oldOffset;
#if hscriptPos
tokenMin = oldTokenMin;
tokenMax = oldTokenMax;
#end
char = -1;

b = new StringBuf();
Expand Down
156 changes: 156 additions & 0 deletions polymod/hscript/_internal/PolymodClassDeclEx.hx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import polymod.hscript._internal.Expr.ClassDecl;
import polymod.hscript._internal.Expr.FieldDecl;
import polymod.hscript._internal.PolymodScriptClass;

using StringTools;

/**
* A scripted class declaration, with a package declaration, imports, and potentially static fields.
*/
Expand Down Expand Up @@ -31,10 +33,17 @@ typedef PolymodClassImport =
@:optional var name:String;
@:optional var pkg:Array<String>;
@:optional var fullPath:String; // pkg.pkg.pkg.name

@:optional var cls:Class<Dynamic>;
@:optional var enm:Enum<Dynamic>;
@:optional var abs:PolymodStaticAbstractReference;
}

/**
* A class which holds a reference to another scripted class,
* for use in a static context. This allows for instantiation,
* or for accessing static fields or methods.
*/
class PolymodStaticClassReference
{
public var cls:PolymodClassDeclEx;
Expand Down Expand Up @@ -125,3 +134,150 @@ class PolymodStaticClassReference
return 'PolymodStaticClassReference(${getFullyQualifiedName()})';
}
}

/**
* A class which holds a reference to an abstract class implementation,
* or (if the implementation class is not available) redirects
* to the abstract static value store built by PolymodScriptClassMacro at compile time.
*/
@:nullSafety
class PolymodStaticAbstractReference
{
/**
* The name of the abstract class, as it was imported.
*/
public var absName:String;

/**
* The internal class that implements the abstract class's static functions,
* if it exists.
*/
public var absImpl:Null<Class<Dynamic>>;

/**
* The path of the implementation class.
* Used for resolving static fields cached at macro time.
*/
public var absImplPath:String;

public function new(absName:String, absImpl:Null<Class<Dynamic>>, absImplPath:String)
{
this.absName = absName;
this.absImpl = absImpl;
this.absImplPath = absImplPath;
}

/**
* Instantiate an instance of the underlying implementation class.
* @param args The arguments to pass to the constructor.
* @return The resulting instance.
* @throws ex Thrown if the underlying implementation class is not available.
*/
public function instantiate(args:Array<Dynamic>):Dynamic
{
if (this.absImpl == null)
{
throw 'Could not resolve abstract class ${absName}.';
}

var ctor = Reflect.field(this.absImpl, '_new');
if (ctor == null)
{
throw 'Could not find constructor for abstract class ${absName}';
}

return Reflect.callMethod(this.absImpl, ctor, args);
}

/**
* Retrieve a static field of the abstract class.
* @param fieldName The name of the field to retrieve.
* @return The value of the field.
*/
public function getField(fieldName:String):Dynamic
{
if (this.absImpl != null)
{
if (Reflect.hasField(this.absImpl, fieldName))
{
var result:Dynamic = Reflect.getProperty(this.absImpl, fieldName);
if (result != null) return result;
}
var getterName:String = 'get_$fieldName';
if (Reflect.hasField(this.absImpl, getterName))
{
var getter = Reflect.field(this.absImpl, getterName);
return Reflect.callMethod(this.absImpl, getter, []);
}
}

return fetchAbstractClassStatic(fieldName);
}

/**
* Assign a static field of the abstract class.
* @param fieldName The name of the field to assign.
* @param fieldValue The value to assign to the field.
* @return The value of the field.
*/
public function setField(fieldName:String, fieldValue:Dynamic):Dynamic
{
if (this.absImpl != null)
{
if (Reflect.hasField(this.absImpl, fieldName))
{
Reflect.setProperty(this.absImpl, fieldName, fieldValue);
return fieldValue;
}
var setterName:String = 'set_$fieldName';
if (Reflect.hasField(this.absImpl, setterName))
{
var setter = Reflect.field(this.absImpl, setterName);
var result = Reflect.callMethod(this.absImpl, setter, [fieldValue]);
return result;
}
}

throw 'Could not resolve abstract static field ${fieldName}';
}

/**
* Call a static function of the abstract class.
* @param funcName The name of the function to call.
* @param args The arguments to pass to the function.
* @return The return value of the function.
*/
public function callFunction(funcName:String, args:Array<Dynamic>):Dynamic
{
// If we can just call the method directly, do that.
var func = getField(funcName);
if (func != null)
{
return Reflect.callMethod(this.absImpl, func, args);
}

throw 'Could not resolve abstract class static function ${funcName}';
}

function fetchAbstractClassStatic(fieldName:String):Dynamic
{
var key:String = '${this.absImplPath}.${fieldName}';

if (PolymodScriptClass.abstractClassStatics.exists(key))
{
var holder = PolymodScriptClass.abstractClassStatics.get(key);
var property = key.replace('.', '_');

return Reflect.getProperty(holder, property);
}
else
{
throw 'Could not resolve abstract class static field ${fieldName}';
}
}

public function toString():String
{
return 'PolymodStaticAbstractReference(${absName} => ${absImpl})';
}
}
85 changes: 60 additions & 25 deletions polymod/hscript/_internal/PolymodInterpEx.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package polymod.hscript._internal;

import polymod.hscript._internal.Expr;
import polymod.hscript._internal.PolymodClassDeclEx.PolymodClassImport;
import polymod.hscript._internal.PolymodClassDeclEx.PolymodStaticAbstractReference;
import polymod.hscript._internal.PolymodClassDeclEx.PolymodStaticClassReference;
import polymod.hscript._internal.PolymodExprEx;
import polymod.hscript._internal.Printer;
Expand Down Expand Up @@ -114,15 +115,26 @@ class PolymodInterpEx extends Interp
// Ignore importedClass.enm as enums cannot be instantiated.

// Importing a blacklisted module creates an import with a `null` class, so we check for that here.
var c = importedClass.cls;
if (c == null)
var abs = importedClass.abs;
if (abs != null)
{
errorEx(EBlacklistedModule(importedClass.fullPath));
try
{
return abs.instantiate(args);
}
catch (e)
{
errorEx(EInvalidModule(importedClass.fullPath));
}
}
else

var cls = importedClass.cls;
if (cls != null)
{
return Type.createInstance(c, args);
return Type.createInstance(cls, args);
}

errorEx(EBlacklistedModule(importedClass.fullPath));
}

// Attempt to resolve the class without overrides.
Expand All @@ -144,6 +156,11 @@ class PolymodInterpEx extends Interp
// Force call super function.
return super.fcall(o, '__super_${f}', args);
}
else if (Std.isOfType(o, PolymodStaticAbstractReference))
{
var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference);
return ref.callFunction(f, args);
}
else if (Std.isOfType(o, PolymodStaticClassReference))
{
var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference);
Expand Down Expand Up @@ -339,7 +356,7 @@ class PolymodInterpEx extends Interp
continue;
}

Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not import ${imp.fullPath}', clsPath);
Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not import ${imp.fullPath}. Check to ensure the module exists and is spelled correctly.', clsPath);
}

// Check if the scripted classes extend the right type.
Expand All @@ -353,15 +370,15 @@ class PolymodInterpEx extends Interp
case CTPath(path, params):
if (params != null && params.length > 0)
{
Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}, do not include type parameters in super class name', clsPath);
Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}, do not include type parameters in super class name.', clsPath);
}

default:
// Other error handling?
}

// Default
Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}, is the type imported?', clsPath);
Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}. Make sure the type to extend has been imported.', clsPath);
}
else
{
Expand Down Expand Up @@ -1276,7 +1293,13 @@ class PolymodInterpEx extends Interp
}

// Otherwise, we assume the field is fine to use.
if (Std.isOfType(o, PolymodStaticClassReference))
if (Std.isOfType(o, PolymodStaticAbstractReference))
{
var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference);

return ref.getField(f);
}
else if (Std.isOfType(o, PolymodStaticClassReference))
{
var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference);

Expand Down Expand Up @@ -1337,12 +1360,6 @@ class PolymodInterpEx extends Interp
// return result;
}

var abstractKey:String = '$oCls.$f';
if (PolymodScriptClass.abstractClassStatics.exists(abstractKey))
{
return Reflect.getProperty(PolymodScriptClass.abstractClassStatics[abstractKey], abstractKey.replace('.', '_'));
}

// Default behavior
if (Reflect.hasField(o, f))
{
Expand Down Expand Up @@ -1386,7 +1403,20 @@ class PolymodInterpEx extends Interp
}

// Otherwise, we assume the field is fine to use.
if (Std.isOfType(o, PolymodStaticClassReference))
if (Std.isOfType(o, PolymodStaticAbstractReference))
{
var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference);

try
{
return ref.setField(f, v);
}
catch (e:Dynamic)
{
errorEx(EInvalidFinalSet(f));
}
}
else if (Std.isOfType(o, PolymodStaticClassReference))
{
var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference);

Expand Down Expand Up @@ -1512,6 +1542,7 @@ class PolymodInterpEx extends Interp
{
if (importedClass.cls != null) return importedClass.cls;
if (importedClass.enm != null) return importedClass.enm;
if (importedClass.abs != null) return importedClass.abs;

// Resolve imported scripted classes.
var result = PolymodStaticClassReference.tryBuild(importedClass.fullPath);
Expand Down Expand Up @@ -1917,7 +1948,7 @@ class PolymodInterpEx extends Interp
}
}

public function registerModules(module:Array<ModuleDecl>, ?origin:String = "hscript")
public function registerModules(module:Array<ModuleDecl>, ?origin:String = "hscript"):Void
{
var pkg:Array<String> = null;
var imports:Map<String, PolymodClassImport> = [];
Expand Down Expand Up @@ -1967,7 +1998,8 @@ class PolymodInterpEx extends Interp
pkg: path.slice(0, path.length - 1),
fullPath: path.join("."),
cls: null,
enm: null
enm: null,
abs: null
};

if (PolymodScriptClass.importOverrides.exists(importedClass.fullPath))
Expand All @@ -1980,9 +2012,11 @@ class PolymodInterpEx extends Interp
else if (PolymodScriptClass.abstractClassImpls.exists(importedClass.fullPath))
{
// We used a macro to map each abstract to its implementation.
importedClass.cls = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath);
trace('RESOLVED ABSTRACT CLASS ${importedClass.fullPath} -> ${Type.getClassName(importedClass.cls)}');
trace(Type.getClassFields(importedClass.cls));
importedClass.abs = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath);
}
else if (PolymodScriptClass.typedefs.exists(importedClass.fullPath))
{
importedClass.cls = PolymodScriptClass.typedefs.get(importedClass.fullPath);
}
else if (_scriptEnumDescriptors.exists(importedClass.fullPath))
{
Expand Down Expand Up @@ -2038,7 +2072,8 @@ class PolymodInterpEx extends Interp
pkg: path.slice(0, path.length - 1),
fullPath: path.join("."),
cls: null,
enm: null
enm: null,
abs: null
};

if (PolymodScriptClass.importOverrides.exists(importedClass.fullPath))
Expand All @@ -2051,9 +2086,7 @@ class PolymodInterpEx extends Interp
else if (PolymodScriptClass.abstractClassImpls.exists(importedClass.fullPath))
{
// We used a macro to map each abstract to its implementation.
importedClass.cls = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath);
trace('RESOLVED ABSTRACT CLASS ${importedClass.fullPath} -> ${Type.getClassName(importedClass.cls)}');
trace(Type.getClassFields(importedClass.cls));
importedClass.abs = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath);
}
else if (_scriptEnumDescriptors.exists(importedClass.fullPath))
{
Expand Down Expand Up @@ -2157,7 +2190,9 @@ class PolymodInterpEx extends Interp
_clone._nextCallObject = this._nextCallObject;
_clone._classDeclOverride = this._classDeclOverride;
_clone.depth = this.depth;
#if hscriptPos
_clone.curExpr = this.curExpr;
#end
_clone.inTry = this.inTry;
return _clone;
}
Expand Down
Loading