diff --git a/src/main/java/org/perlonjava/perlmodule/Exporter.java b/src/main/java/org/perlonjava/perlmodule/Exporter.java index 2a8898c3c..b992194cc 100644 --- a/src/main/java/org/perlonjava/perlmodule/Exporter.java +++ b/src/main/java/org/perlonjava/perlmodule/Exporter.java @@ -155,9 +155,9 @@ public static RuntimeList exportToLevel(RuntimeArray args, int ctx) { String caller = callerList.scalar().toString(); // Retrieve the export lists and tags from the package - RuntimeArray export = GlobalVariable.getGlobalArray(packageScalar + "::EXPORT"); - RuntimeArray exportOk = GlobalVariable.getGlobalArray(packageScalar + "::EXPORT_OK"); - RuntimeHash exportTags = GlobalVariable.getGlobalHash(packageScalar + "::EXPORT_TAGS"); + RuntimeArray export = GlobalVariable.getGlobalArray(packageName + "::EXPORT"); + RuntimeArray exportOk = GlobalVariable.getGlobalArray(packageName + "::EXPORT_OK"); + RuntimeHash exportTags = GlobalVariable.getGlobalHash(packageName + "::EXPORT_TAGS"); // If no specific symbols are requested, default to exporting all symbols in @EXPORT if (args.size() == 0) { @@ -200,9 +200,9 @@ public static RuntimeList exportToLevel(RuntimeArray args, int ctx) { for (RuntimeBase symbolObj : tagArray.elements) { String symbolString = symbolObj.toString(); - boolean isExported = export.elements.stream() + boolean isExported = export != null && export.elements.stream() .anyMatch(e -> e.toString().equals(symbolString)); - boolean isExportOk = exportOk.elements.stream() + boolean isExportOk = exportOk != null && exportOk.elements.stream() .anyMatch(e -> e.toString().equals(symbolString)); if (!isExported && !isExportOk && !symbolString.matches("^[$@%*]")) { @@ -213,9 +213,9 @@ public static RuntimeList exportToLevel(RuntimeArray args, int ctx) { } else { finalSymbolString = "&" + symbolString; } - isExported = export.elements.stream() + isExported = export != null && export.elements.stream() .anyMatch(e -> e.toString().equals(finalSymbolString)); - isExportOk = exportOk.elements.stream() + isExportOk = exportOk != null && exportOk.elements.stream() .anyMatch(e -> e.toString().equals(finalSymbolString)); } @@ -277,7 +277,15 @@ private static void importFunction(String packageName, String caller, String fun RuntimeScalar exportSymbol = GlobalVariable.getGlobalCodeRef(packageName + "::" + functionName); if (exportSymbol.type == RuntimeScalarType.CODE) { String fullName = caller + "::" + functionName; - GlobalVariable.getGlobalCodeRef(fullName).set(exportSymbol); + RuntimeScalar importedRef = GlobalVariable.getGlobalCodeRef(fullName); + importedRef.set(exportSymbol); + + // If the exported symbol is a forward declaration (undefined), annotate it with the source package + // so that AUTOLOAD can be resolved from the original package + if (exportSymbol.value instanceof RuntimeCode code && !code.defined()) { + code.sourcePackage = packageName; + } + // If this function name is an overridable operator (like 'time'), mark it in isSubs // so the parser knows to treat it as a subroutine call instead of the builtin if (ParserTables.OVERRIDABLE_OP.contains(functionName)) { diff --git a/src/main/java/org/perlonjava/runtime/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/RuntimeCode.java index 688153db1..484fa8a50 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeCode.java @@ -70,6 +70,8 @@ protected boolean removeEldestEntry(Map.Entry, MethodHandle> eldest) { // Method context information for next::method support public String packageName; public String subName; + // Source package for imported forward declarations (used for AUTOLOAD resolution) + public String sourcePackage = null; // Flag to indicate this is a symbolic reference created by \&{string} that should always be "defined" public boolean isSymbolicReference = false; // Flag to indicate this is a built-in operator @@ -587,7 +589,7 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, RuntimeArray a, int // Try to find AUTOLOAD for this subroutine String subroutineName = code.packageName + "::" + code.subName; if (code.packageName != null && code.subName != null && !subroutineName.isEmpty()) { - // Check if AUTOLOAD exists + // First check if AUTOLOAD exists in the current package String autoloadString = code.packageName + "::AUTOLOAD"; RuntimeScalar autoload = GlobalVariable.getGlobalCodeRef(autoloadString); if (autoload.getDefinedBoolean()) { @@ -596,6 +598,19 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, RuntimeArray a, int // Call AUTOLOAD return apply(autoload, a, callContext); } + + // If this is an imported forward declaration, check AUTOLOAD in the source package + if (code.sourcePackage != null && !code.sourcePackage.equals(code.packageName)) { + String sourceAutoloadString = code.sourcePackage + "::AUTOLOAD"; + RuntimeScalar sourceAutoload = GlobalVariable.getGlobalCodeRef(sourceAutoloadString); + if (sourceAutoload.getDefinedBoolean()) { + // Set $AUTOLOAD name to the original package function name + String sourceSubroutineName = code.sourcePackage + "::" + code.subName; + getGlobalVariable(sourceAutoloadString).set(sourceSubroutineName); + // Call AUTOLOAD from the source package + return apply(sourceAutoload, a, callContext); + } + } } throw new PerlCompilerException("Undefined subroutine &" + subroutineName + " called at "); }