From ec01692883e45d6818749225ff2734534da8bdcb Mon Sep 17 00:00:00 2001 From: Jaime Humberto Macias Bustamante Date: Thu, 23 Oct 2025 11:07:32 -0700 Subject: [PATCH] Update CustomClassMacro.hx --- hscript/macros/CustomClassMacro.hx | 357 ++++++++++++++++++++++++++--- 1 file changed, 331 insertions(+), 26 deletions(-) diff --git a/hscript/macros/CustomClassMacro.hx b/hscript/macros/CustomClassMacro.hx index 1ab1e11..eed4b85 100644 --- a/hscript/macros/CustomClassMacro.hx +++ b/hscript/macros/CustomClassMacro.hx @@ -12,73 +12,109 @@ import haxe.macro.Type.ClassField; import haxe.macro.Type.VarAccess; import haxe.macro.*; +using haxe.macro.ComplexTypeTools; +using haxe.macro.ExprTools; +using haxe.macro.TypeTools; using StringTools; using Lambda; +typedef ClassInfo = { + var imports:Array; + var ?params:Array; +} + @:nullSafety(Loose) class CustomClassMacro { public static inline final FUNC_PREFIX = "_HX_SUPER__"; public static inline final CLASS_SUFFIX = "_HSX"; public static var unallowedMetas:Array = [":bitmap", ":noCustomClass", ":generic"]; - private static var classImports:Map> = []; + private static var classesToBuild:Map = []; public static function init() { #if !display #if CUSTOM_CLASSES if(Context.defined("display")) return; for(apply in Config.ALLOWED_CUSTOM_CLASSES) { - //Compiler.addGlobalMetadata(apply, "@:build(hscript.macros.CustomClassMacro.fetchImports())"); + //Compiler.addGlobalMetadata(apply, "@:build(hscript.macros.CustomClassMacro.fetchClass())"); } - //Context.onAfterTyping(buildTyped); + //Context.onAfterTyping(build); #end #end } - public static function fetchImports():Array { + public static function fetchClass():Array { var fields = Context.getBuildFields(); var clRef = Context.getLocalClass(); if (clRef == null) return fields; var cl = clRef.get(); if (cl.isAbstract || cl.isExtern || cl.isFinal || cl.isInterface) return fields; if (!cl.name.endsWith("_Impl_") && !cl.name.endsWith(CLASS_SUFFIX) && !cl.name.endsWith("_HSC")) { + var metas = cl.meta.get(); + + for(m in metas) + if (unallowedMetas.contains(m.name)) + return fields; + + // if(cl.params.length > 0) + // return fields; + var key = cl.module; var fkey = '${cl.module}.${cl.name}'; + if(key == "sys.thread.FixedThreadPool") return fields; // Error: Type name sys.thread.Worker_HSX is redefined from module sys.thread.FixedThreadPool + if(key == "StdTypes") return fields; // Error: Cant extend basic class + if(key == "Xml") return fields; // Error: Cant extend basic class + if(key == "Date") return fields; // Error: Cant extend basic class + if(key == "away3d.tools.commands.Mirror") return fields; // Error: Unknown identifier + if(key == "away3d.tools.commands.SphereMaker") return fields; // Error: Unknown identifier + if(key == "away3d.tools.commands.Weld") return fields; // Error: Unknown identifier + if(fkey == "hscript.CustomClassHandler.TemplateClass") return fields; // Error: Redefined + if(fkey == "hscript.CustomClassHandler.CustomTemplateClass") return fields; // Error: Redefined + if(fkey == "hscript.CustomClass") return fields; // Error: Redefined + if(key == "sys.thread.EventLoop") return fields; // Error: cant override force inlined if (Config.DISALLOW_CUSTOM_CLASSES.contains(key) || Config.DISALLOW_CUSTOM_CLASSES.contains(fkey)) return fields; if (key.contains("_")) return fields; - + var imports = Context.getLocalImports().copy(); - classImports[fkey] = imports; + var clsDecl:ClassInfo = { + imports: imports, + params: cl.params.length > 0 ? cl.params : null + } + + classesToBuild.set(fkey, clsDecl); } return fields; } - public static function build(modules:Array) { - var classesToApply:Array = []; + // called in an onAfterTyping callback + public static function build(modules:Array) { + var classesToApply:Array = []; for(m in modules) { - switch(m) { + switch (m) { case TClassDecl(c): if(c == null) continue; var cl = c.get(); + var fkey = '${cl.module}.${cl.name}'; + if (classesToBuild.exists(fkey) && classesToBuild.get(fkey).params == null) { + classesToApply.push(cl); + } + /* if(cl.isAbstract || cl.isExtern || cl.isFinal || cl.isInterface) continue; if(!cl.name.endsWith("_Impl_") && !cl.name.endsWith(CLASS_SUFFIX) && !cl.name.endsWith("_HSC")) { - var key = cl.module; var fkey = '${cl.module}.${cl.name}'; - if(Config.DISALLOW_CUSTOM_CLASSES.contains(key) || Config.DISALLOW_CUSTOM_CLASSES.contains(fkey)) continue; - if(key.contains("_")) continue; - var isValid = Config.ALLOWED_CUSTOM_CLASSES.exists((s:String) -> return fkey.startsWith(s) || key.startsWith(s)); - if (isValid) { + if(classesToBuild.exists(fkey) && classesToBuild.get(fkey).params == null) { classesToApply.push(cl); - } + } } + */ default: } } if(classesToApply.length > 0) buildClasses(classesToApply); - } + } private static function buildClasses(classes:Array) { for(cl in classes) @@ -87,6 +123,10 @@ class CustomClassMacro { private static function buildClass(cl:ClassType) { var classFields:Array = cl.fields.get(); + var classParams:Map = cl.params.length == 0 ? [] : [ + for (i in 0...cl.params.length) + '${cl.pack.join(".")}.${cl.name}.${cl.params[i].name}' => cl.params[i].t + ]; var fields:Array = classFields.concat(getSuperFields(cl)); var shadowClass:TypeDefinition = macro class { @@ -103,7 +143,6 @@ class CustomClassMacro { hasNew = true; switch(f.type) { case TFun(args, ret): - // TODO: constructor var constArgs:Array = [ for (arg in args) {name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)} @@ -143,10 +182,7 @@ class CustomClassMacro { default: } case FVar(read, write): - switch([read, write]) { - case [AccInline, AccInline]: continue; - default: - } + continue; } if(f.name == "hget" || f.name == "hset") continue; @@ -158,7 +194,7 @@ class CustomClassMacro { switch (f.kind) { case FMethod(k): - var newFields = overrideField(f, cl); + var newFields = overrideField(f, cl, f.type, classParams); var overField:Field = newFields[0]; var superField:Field = newFields[1]; @@ -187,7 +223,7 @@ class CustomClassMacro { ], false, true, false); var fkey = '${cl.module}.${cl.name}'; - var imports = classImports[fkey]; + var imports = classesToBuild[fkey].imports; if(imports != null) { Utils.setupMetas(shadowClass, imports); Utils.processImport(imports, "hscript.utils.UnsafeReflect", "UnsafeReflect"); @@ -471,14 +507,14 @@ class CustomClassMacro { Context.defineModule(cl.module, [shadowClass], null); } - private static function overrideField(field:ClassField, cl:ClassType, ?type:haxe.macro.Type):Array { + private static function overrideField(field:ClassField, cl:ClassType, ?type:haxe.macro.Type, ?params:Map):Array { if(type == null) type = field.type; switch(type) { case TLazy(f): var fv = f(); - return overrideField(field, cl, fv); + return overrideField(field, cl, fv, params); case TFun(args, ret): for (a in args) switch (a.t) { @@ -489,7 +525,7 @@ class CustomClassMacro { // Inline functions are already skipped // As well for "@:generic" fields - var inputArgs:Array = []; + var fnInputArgs:Array = []; if(field.expr() == null) return []; @@ -498,7 +534,20 @@ class CustomClassMacro { switch(field.expr().expr) { case TFunction(tfunc): for(arg in tfunc.args) { + var opt = (arg.value != null); + var fnMeta = arg.v.meta.get(); + var fnExpr:Expr = arg.value == null ? null : Context.getTypedExpr(arg.value); + // The argument type. We have to handle any type parameters, and deparameterizeType does so recursively. + var fnType:ComplexType = Context.toComplexType(deparameterizeType(arg.v.t, params)); // TODO + var fnArg:FunctionArg = { + name: arg.v.name, + type: fnType, + value: fnExpr, + meta: fnMeta + }; + + fnInputArgs.push(fnArg); } case TConst(_): return []; @@ -507,6 +556,67 @@ class CustomClassMacro { // TODO + var returnsVoid:Bool = ret.toString() == "Void"; + + var fnCall:Array = [for(a in args) macro $i{a.name}]; + var fnParams:Array = [for(fp in field.params) {name: fp.name}]; + var fnRet = returnsVoid ? null : Context.toComplexType(deparameterizeType(ret, params)); + var fnName = field.name; + + var overField:Field = { + name: fnName, + access: fnAccess, + pos: field.pos, + meta: field.meta.get(), + kind: FFun({ + args: fnInputArgs, + params: fnParams, + ret: fnRet, + expr: macro { + var name:String = $v{fnName}; + + if (__interp != null && __class__fields.contains(name)) { + var v:Dynamic = null; + if (Reflect.isFunction(v = __interp.variables.get(name))) { + ${ + !returnsVoid + ? (macro return v($a{fnCall})) + : macro { + v($a{fnCall}); + return; + } + } + } + } + + ${ + !returnsVoid + ? (macro return super.$fnName($a{fnCall})) + : (macro super.$fnName($a{fnCall})) + } + } + }) + } + + var superFunField:Field = { + name: '$FUNC_PREFIX${fnName}', + pos: field.pos, + access: [APrivate], + meta: field.meta.get(), + kind: FFun({ + args: fnInputArgs, + params: fnParams, + ret: fnRet, + expr: returnsVoid ? { + macro return super.$fnName($a{fnCall}); + } : { + macro super.$fnName($a{fnCall}); + } + }) + } + + return [overField, superFunField]; + /* case TInst(_, _): return []; case TEnum(_, _): return []; case TMono(_): return []; @@ -518,6 +628,201 @@ class CustomClassMacro { return []; } + static function getBaseParamsOfType(parentType:haxe.macro.Type, paramTypes:Array):Array + { + var parentParams:Array = []; + + switch (parentType) + { + case TMono(t): + var ty = t.get(); + return getBaseParamsOfType(ty, paramTypes); + + case TInst(t, params): + // Continue + parentParams = t.get().params; + + case TType(t, params): + // Recurse + var ty:haxe.macro.Type = t.get().type; + return getBaseParamsOfType(ty, paramTypes); + + case TDynamic(t): + // Recurse + return getBaseParamsOfType(t, paramTypes); + + case TLazy(f): + // Recurse + var ty:haxe.macro.Type = f(); + return getBaseParamsOfType(ty, paramTypes); + + case TAbstract(t, _params): + // Continue + parentParams = t.get().params; + + // case TEnum(t:Ref, params:Array): + // case TFun(args:Array<{name:String, opt:Bool, t:Type}>, ret:Type): + // case TAnonymous(a:Ref): + default: + //Context.error('Unsupported type: ${parentType}', Context.currentPos()); + } + + var result:Array = []; + + for (i => parentParam in parentParams) + { + var newParam:haxe.macro.Type.TypeParameter = { + name: parentParam.name, + t: paramTypes[i], + }; + result.push(newParam); + } + + return result; + } + + static function scanBaseTypes(targetType:haxe.macro.Type):Array { + switch (targetType) { + case TFun(args, ret): + var results:Array = []; + + for (result in scanBaseTypes(ret)) { + results.push(result); + } + for (arg in args) { + for (result in scanBaseTypes(arg.t)) { + results.push(result); + } + } + return results; + case TAbstract(ty, params): + if (params.length == 0) { + return [targetType]; + } else { + var results:Array = []; + for (param in params) { + for (result in scanBaseTypes(param)) { + results.push(result); + } + } + return results; + } + default: + return [targetType]; + } + } + + static function deparameterizeType(targetType:haxe.macro.Type, targetParams:Map):haxe.macro.Type { + var resultType:haxe.macro.Type = targetType; + + switch (targetType) { + case TFun(args, ret): + // Function type. + // This is not referring to functions of a class, but rather a function taken as a parameter (like a callback). + + // Deparameterize the return type. + var retType:haxe.macro.Type = deparameterizeType(ret, targetParams); + // Deparameterize the argument types. + var argTypes:Array<{name:String, opt:Bool, t:haxe.macro.Type}> = args.map(function(arg) { + return { + name: arg.name, + opt: arg.opt, + t: deparameterizeType(arg.t, targetParams), + }; + }); + + // Construct the new type. + resultType = TFun(argTypes, retType); + + case TAbstract(ty, params): + // Abstract type. Sometimes used by types like Null. + + var name = ty.toString(); + var typ = ty.get(); + + // Check if the Abstract type is a parameter we recognize and can replace. + if (targetParams.exists(ty.toString())) { + // If so, replace it with the real type. + resultType = targetParams.get(ty.toString()); + // recursive call in case result is a parameter + resultType = deparameterizeType(resultType, targetParams); + } else if (params.length != 0) { + var oldParams:Array = []; + var newParams:Array = []; + for (param in params) { + var baseTypes = scanBaseTypes(param); + + for (baseType in baseTypes) { + var newParam = deparameterizeType(baseType, targetParams); + if (newParam.toString() == "Void") { + // Skipping Void... + } else { + oldParams.push(baseType); + newParams.push(newParam); + } + } + } + var baseParams = getBaseParamsOfType(resultType, oldParams); + newParams = newParams.slice(0, baseParams.length); + + if (newParams.length > 0) { + // Context.info('Building new abstract (${baseParams} + ${newParams})...', Context.currentPos()); + resultType = resultType.applyTypeParameters(baseParams, newParams); + // Context.info('Deparameterized abstract type: ${resultType.toString()}', Context.currentPos()); + } else { + // Leave the type as is. + } + } else { + // Else, there are no parameters related this type and we don't need to mutate it. + } + case TInst(ty, params): + // Instance type. Used by most variables. + + // Check if the Instance type is a parameter we recognize and can replace. + if (targetParams.exists(ty.toString())) { + // If so, replace it with the real type. + resultType = targetParams.get(ty.toString()); + // recursive call in case result is a parameter + resultType = deparameterizeType(resultType, targetParams); + } else if (params.length != 0) { + var oldParams:Array = []; + var newParams:Array = []; + for (param in params) { + var baseTypes = scanBaseTypes(param); + + for (baseType in baseTypes) { + var newParam = deparameterizeType(baseType, targetParams); + if (newParam.toString() == "Void") { + // Skipping Void... + } else { + oldParams.push(baseType); + newParams.push(newParam); + } + } + } + var baseParams = getBaseParamsOfType(resultType, oldParams); + newParams = newParams.slice(0, baseParams.length); + + if (newParams.length > 0) { + // Context.info('Building new abstract (${baseParams} + ${newParams})...', Context.currentPos()); + resultType = resultType.applyTypeParameters(baseParams, newParams); + // Context.info('Deparameterized abstract type: ${resultType.toString()}', Context.currentPos()); + } else { + // Leave the type as is. + } + } else { + // Else, there are no parameters related this type and we don't need to mutate it. + } + + default: + // Do nothing. + // Muted because I haven't actually seen any issues caused by this. Maybe investigate in the future. + // Context.warning('You failed to handle this! ${targetType}', Context.currentPos()); + } + + return resultType; + } + private static function getSuperFields(cl:ClassType):Array { var fields:Array = []; var superClsRef = cl.superClass;