diff --git a/src/main/java/org/perlonjava/perlmodule/Exporter.java b/src/main/java/org/perlonjava/perlmodule/Exporter.java index b992194cc..7248b03be 100644 --- a/src/main/java/org/perlonjava/perlmodule/Exporter.java +++ b/src/main/java/org/perlonjava/perlmodule/Exporter.java @@ -153,6 +153,11 @@ public static RuntimeList exportToLevel(RuntimeArray args, int ctx) { // Determine the caller's namespace RuntimeList callerList = RuntimeCode.caller(new RuntimeList(exportLevel), SCALAR); String caller = callerList.scalar().toString(); + if (caller == null || caller.isEmpty()) { + // In standard Perl, missing/empty caller context behaves like package 'main'. + // This is critical for `use Some::Module qw(...)` at top-level. + caller = "main"; + } // Retrieve the export lists and tags from the package RuntimeArray export = GlobalVariable.getGlobalArray(packageName + "::EXPORT"); @@ -278,12 +283,24 @@ private static void importFunction(String packageName, String caller, String fun if (exportSymbol.type == RuntimeScalarType.CODE) { String fullName = caller + "::" + functionName; 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 (exportSymbol.value instanceof RuntimeCode exportedCode) { + if (exportedCode.defined()) { + // Fully defined sub: import by aliasing the CODE ref. + importedRef.set(exportSymbol); + } else { + // Forward declaration: standard Perl semantics allow this to be called and + // resolved via AUTOLOAD in the defining package. + // + // The importedRef and exportSymbol point to DIFFERENT RuntimeCode objects + // (one for main::GetAllTags, one for Package::GetAllTags). + // We need to make the imported one resolve via the exporting package's AUTOLOAD. + if (importedRef.value instanceof RuntimeCode importedCode) { + importedCode.sourcePackage = packageName; + // Also copy the subName to ensure it's set correctly + importedCode.subName = functionName; + } + } } // If this function name is an overridable operator (like 'time'), mark it in isSubs diff --git a/src/main/java/org/perlonjava/runtime/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/RuntimeCode.java index f7cfe6892..9a36656af 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeCode.java @@ -651,17 +651,8 @@ 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()) { - // First check if AUTOLOAD exists in the current package - String autoloadString = code.packageName + "::AUTOLOAD"; - RuntimeScalar autoload = GlobalVariable.getGlobalCodeRef(autoloadString); - if (autoload.getDefinedBoolean()) { - // Set $AUTOLOAD name - getGlobalVariable(autoloadString).set(subroutineName); - // Call AUTOLOAD - return apply(autoload, a, callContext); - } - - // If this is an imported forward declaration, check AUTOLOAD in the source package + // If this is an imported forward declaration, check AUTOLOAD in the source package FIRST + // This matches Perl semantics where imported subs resolve via the exporting package's AUTOLOAD if (code.sourcePackage != null && !code.sourcePackage.equals(code.packageName)) { String sourceAutoloadString = code.sourcePackage + "::AUTOLOAD"; RuntimeScalar sourceAutoload = GlobalVariable.getGlobalCodeRef(sourceAutoloadString); @@ -673,6 +664,16 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, RuntimeArray a, int return apply(sourceAutoload, a, callContext); } } + + // Then check if AUTOLOAD exists in the current package + String autoloadString = code.packageName + "::AUTOLOAD"; + RuntimeScalar autoload = GlobalVariable.getGlobalCodeRef(autoloadString); + if (autoload.getDefinedBoolean()) { + // Set $AUTOLOAD name + getGlobalVariable(autoloadString).set(subroutineName); + // Call AUTOLOAD + return apply(autoload, a, callContext); + } } throw new PerlCompilerException("Undefined subroutine &" + subroutineName + " called at "); } @@ -746,12 +747,24 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineNa } if (!fullSubName.isEmpty()) { - // Check if AUTOLOAD exists + // If this is an imported forward declaration, check AUTOLOAD in the source package FIRST + // This matches Perl semantics where imported subs resolve via the exporting package's AUTOLOAD + if (code.sourcePackage != null && !code.sourcePackage.isEmpty()) { + 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); + } + } + + // Then check if AUTOLOAD exists in the current package String autoloadString = fullSubName.substring(0, fullSubName.lastIndexOf("::") + 2) + "AUTOLOAD"; - // System.err.println("AUTOLOAD: " + fullName); RuntimeScalar autoload = GlobalVariable.getGlobalCodeRef(autoloadString); if (autoload.getDefinedBoolean()) { - // System.err.println("AUTOLOAD exists: " + fullName); // Set $AUTOLOAD name getGlobalVariable(autoloadString).set(fullSubName); // Call AUTOLOAD diff --git a/src/test/resources/unit/exiftool_exporter_autoload.t b/src/test/resources/unit/exiftool_exporter_autoload.t new file mode 100644 index 000000000..1ef9e9e5a --- /dev/null +++ b/src/test/resources/unit/exiftool_exporter_autoload.t @@ -0,0 +1,40 @@ +use strict; +use warnings; + +our $autoload_seen; + +{ + package Exiftool::ExporterAutoload; + + use Exporter qw(import); + + our @EXPORT = qw(GetAllTags); + + sub GetAllTags; + + our $AUTOLOAD; + + sub AUTOLOAD { + $main::autoload_seen = $AUTOLOAD; + return 1; + } +} + +print "1..1\n"; + +Exiftool::ExporterAutoload->import(); + +my $ok = eval { GetAllTags(); 1 }; +if (!$ok) { + my $err = $@; + $err =~ s/\s+\z//; + print "not ok 1 - GetAllTags() should trigger AUTOLOAD (got error: $err)\n"; + exit; +} + +if (defined $autoload_seen && $autoload_seen eq 'Exiftool::ExporterAutoload::GetAllTags') { + print "ok 1 - AUTOLOAD called for imported forward-declared sub\n"; +} else { + my $seen = defined $autoload_seen ? $autoload_seen : ''; + print "not ok 1 - AUTOLOAD not called correctly (saw: $seen)\n"; +}