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
4 changes: 2 additions & 2 deletions 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 = "8333cd0ba";
public static final String gitCommitId = "6d1d90197";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand All @@ -48,7 +48,7 @@ public final class Configuration {
* Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at"
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String buildTimestamp = "Apr 30 2026 14:33:22";
public static final String buildTimestamp = "Apr 30 2026 15:01:59";

// Prevent instantiation
private Configuration() {
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/org/perlonjava/runtime/perlmodule/Internals.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public static void initialize() {
internals.registerMethod("V", "V", null);
internals.registerMethod("getcwd", "getcwd", null);
internals.registerMethod("abs_path", "abs_path", ";$");
// PerlOnJava-only probe: report whether a fully qualified sub
// name was installed via typeglob assignment (e.g. Exporter
// imports do `*Dst::name = \&Src::name`). Used by B::GV::GvFLAGS
// to approximate the real-Perl GVf_IMPORTED_CV bit so callers
// such as Pod::Coverage can skip imported helpers.
internals.registerMethod("jperl_is_imported_sub", "jperl_is_imported_sub", "$");
} catch (NoSuchMethodException e) {
System.err.println("Warning: Missing Internals method: " + e.getMessage());
}
Expand Down Expand Up @@ -502,4 +508,26 @@ public static RuntimeList abs_path(RuntimeArray args, int ctx) {
return new RuntimeScalar().getList(); // return undef on error
}
}

/**
* Returns 1 if the named sub was installed via typeglob assignment
* (i.e. Exporter-style import) rather than defined directly with
* {@code sub name { ... }}, otherwise empty list (undef).
*
* <p>PerlOnJava tracks this in
* {@link org.perlonjava.runtime.runtimetypes.GlobalVariable#isSubs};
* we expose it so {@code B::GV::GvFLAGS} can approximate the real-Perl
* {@code GVf_IMPORTED_CV} bit. Pod::Coverage uses this to skip
* imported helpers when reporting coverage.
*
* @param args The fully-qualified sub name (e.g. {@code "Pkg::name"}).
*/
public static RuntimeList jperl_is_imported_sub(RuntimeArray args, int ctx) {
if (args.size() == 0) return new RuntimeScalar().getList();
String name = args.get(0).toString();
if (org.perlonjava.runtime.runtimetypes.GlobalVariable.isSubs.getOrDefault(name, false)) {
return new RuntimeScalar(1).getList();
}
return new RuntimeScalar().getList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,18 @@ public static java.util.List<String> getGlobAliasGroup(String globName) {
return group;
}

/**
* Returns true if {@code globName} participates in a `*A = *B` style glob
* alias relationship. Includes both sides — only one direction is stored
* in {@link #globAliases}, so the canonical destination (e.g. {@code B}
* after {@code *A = *B}) is detected by walking the map values.
*/
public static boolean isInGlobAliasGroup(String globName) {
if (globAliases.isEmpty()) return false;
if (globAliases.containsKey(globName)) return true;
return globAliases.containsValue(globName);
}

/**
* Retrieves a global variable by its key, initializing it if necessary.
* If the key matches a regex capture variable pattern, it initializes a special variable.
Expand Down Expand Up @@ -449,8 +461,35 @@ public static RuntimeArray getGlobalArray(String key) {
}
RuntimeArray var = globalArrays.get(key);
if (var == null) {
var = new RuntimeArray();
globalArrays.put(key, var);
// Glob-aliased names (`*A = *B`) need to share the same RuntimeArray
// so that auto-vivification under one name shows up under the other.
// Fan-out to every alias-group sibling on first creation. We detect
// membership by asking for the alias group itself instead of just
// probing globAliases.containsKey(key) — for `*A = *B`, only one
// direction is recorded in the map, so the canonical name (B) is
// not a key but is still part of the group.
java.util.List<String> aliasGroup = isInGlobAliasGroup(key) ? getGlobAliasGroup(key) : null;
if (aliasGroup != null && aliasGroup.size() > 1) {
for (String alias : aliasGroup) {
RuntimeArray existing = globalArrays.get(alias);
if (existing != null) {
var = existing;
break;
}
}
if (var == null) {
var = new RuntimeArray();
}
for (String alias : aliasGroup) {
globalArrays.putIfAbsent(alias, var);
}
if (!globalArrays.containsKey(key)) {
globalArrays.put(key, var);
}
} else {
var = new RuntimeArray();
globalArrays.put(key, var);
}
}
return var;
}
Expand Down Expand Up @@ -508,17 +547,46 @@ public static RuntimeHash getGlobalHash(String key) {
}
RuntimeHash var = globalHashes.get(key);
if (var == null) {
// Check if this is a package stash (ends with ::)
if (key.endsWith("::")) {
var = new RuntimeStash(key);
boolean isStash = key.endsWith("::");
// Glob-aliased names (`*A = *B`) need to share the same RuntimeHash
// so that auto-vivification under one name shows up under the other.
// Stash-view hashes are excluded — they have their own unification
// path in RuntimeGlob.set(). See getGlobalArray() for the mirror
// logic and the rationale for using isInGlobAliasGroup() (the map
// only records one side of `*A = *B`, so the canonical name has
// to be detected via the values).
java.util.List<String> aliasGroup =
(!isStash && isInGlobAliasGroup(key)) ? getGlobAliasGroup(key) : null;
if (aliasGroup != null && aliasGroup.size() > 1) {
for (String alias : aliasGroup) {
RuntimeHash existing = globalHashes.get(alias);
if (existing != null) {
var = existing;
break;
}
}
if (var == null) {
var = new RuntimeHash();
}
var.isGlobalPackageHash = true;
for (String alias : aliasGroup) {
globalHashes.putIfAbsent(alias, var);
}
if (!globalHashes.containsKey(key)) {
globalHashes.put(key, var);
}
} else {
var = new RuntimeHash();
if (isStash) {
var = new RuntimeStash(key);
} else {
var = new RuntimeHash();
}
// D-W6.18: mark as package-global so values stored here
// get the storedInPackageGlobal flag (replaces class-name
// heuristic in walker gate).
var.isGlobalPackageHash = true;
globalHashes.put(key, var);
}
// D-W6.18: mark as package-global so values stored here
// get the storedInPackageGlobal flag (replaces class-name
// heuristic in walker gate).
var.isGlobalPackageHash = true;
globalHashes.put(key, var);
}
return var;
}
Expand Down
Loading
Loading