Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public final class Configuration {
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String gitCommitId = "1fbd174ea";
public static final String gitCommitId = "ed722c0cd";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,17 @@ public static RuntimeScalar deleteGlobalCodeRefAsScalar(RuntimeScalar key, Strin
return deleteGlobalCodeRefAsScalar(name);
}

/**
* Clears pinned code references for all subroutines in a given namespace.
* This prevents deleted subs from being resurrected by getGlobalCodeRef()
* after stash namespace deletion (e.g., delete $::{"Foo::"}).
*
* @param prefix The namespace prefix (e.g., "Foo::") to clear.
*/
public static void clearPinnedCodeRefsForNamespace(String prefix) {
pinnedCodeRefs.keySet().removeIf(k -> k.startsWith(prefix));
}

/**
* Clears the package existence cache.
* Should be called when new packages are loaded or code refs are modified.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ public RuntimeScalar delete(RuntimeScalar key) {
}

private RuntimeScalar deleteGlob(String k) {
// Special handling for namespace keys (ending with "::")
// e.g., delete $::{"Foo::"} should remove all symbols in the Foo:: namespace
if (k.endsWith("::")) {
return deleteNamespace(k);
}

// For stash, we need to delete from GlobalVariable maps and return the glob
String fullKey = namespace + k;

Expand Down Expand Up @@ -197,6 +203,42 @@ private RuntimeScalar deleteGlob(String k) {
return detached;
}

/**
* Deletes an entire namespace from the stash.
* When Perl does delete $::{"Foo::"}, all symbols in the Foo:: namespace
* should be removed, making Foo->can("bar") return false and preventing
* spurious "Subroutine redefined" warnings when the namespace is re-populated.
*/
private RuntimeScalar deleteNamespace(String k) {
// Compute the prefix for child symbols.
// For main:: stash: symbols are stored as "Foo::bar" (not "main::Foo::bar"),
// so the prefix is just k itself (e.g., "Foo::")
// For other stashes: symbols are stored as "Outer::Inner::bar",
// so the prefix is namespace + k (e.g., "Outer::" + "Inner::" = "Outer::Inner::")
String childPrefix = "main::".equals(namespace) ? k : namespace + k;

// Remove all symbols with this prefix from all global maps (prefix-based removal)
GlobalVariable.globalCodeRefs.keySet().removeIf(key -> key.startsWith(childPrefix));
GlobalVariable.globalVariables.keySet().removeIf(key -> key.startsWith(childPrefix));
GlobalVariable.globalArrays.keySet().removeIf(key -> key.startsWith(childPrefix));
GlobalVariable.globalHashes.keySet().removeIf(key -> key.startsWith(childPrefix));
GlobalVariable.globalIORefs.keySet().removeIf(key -> key.startsWith(childPrefix));
GlobalVariable.globalFormatRefs.keySet().removeIf(key -> key.startsWith(childPrefix));

// Clear pinned code refs so deleted subs don't get resurrected
// by getGlobalCodeRef() lookups (e.g., in SubroutineParser redefinition check)
GlobalVariable.clearPinnedCodeRefsForNamespace(childPrefix);

// Clear stash alias if any
GlobalVariable.clearStashAlias(childPrefix);

// Method resolution and package existence caches are now stale
InheritanceResolver.invalidateCache();
GlobalVariable.clearPackageCache();

return new RuntimeScalar();
}

/**
* Gets the size of the hash.
*
Expand Down
Loading