diff --git a/dev/design/moo_support.md b/dev/design/moo_support.md
index 8b58febb0..a28be37dc 100644
--- a/dev/design/moo_support.md
+++ b/dev/design/moo_support.md
@@ -612,26 +612,36 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas
- This fixed Mo's BUILD feature which depends on the %e merge pattern
- Mo tests: 6/28 failing → 1/28 failing (143/144 subtests pass)
+- [x] Phase 26: Add Sub::Name module and fix @INC hook exception handling (2026-03-16)
+ - Root cause: moo-utils-_subname-Sub-Name.t was failing because:
+ 1. Sub::Name module was not available
+ 2. @INC hooks that threw exceptions were silently ignored
+ - **SubName.java**: Java implementation of Sub::Name::subname(NAME, CODEREF)
+ - **Sub/Name.pm**: Perl wrapper using XSLoader
+ - **ModuleOperators.java fix**:
+ - Previously: catch (Exception e) { return null; } - ignored hook errors
+ - Now: Let exceptions propagate to match Perl's behavior
+ - This allows InlineModule to "hide" modules by having hooks throw die()
+ - moo-utils-_subname-Sub-Name.t: 0/2 → 2/2 passing
+
### Current Status
-**Test Results (after Phase 25):**
-- **Moo**: 62/71 test programs passing (87%), 768/829 subtests passing (93%)
+**Test Results (after Phase 26):**
+- **Moo**: 63/71 test programs passing (89%), 770/829 subtests passing (93%)
- **Mo**: 27/28 test programs passing (99.3%), 143/144 subtests passing
**Remaining Failures (categorized):**
1. **accessor-weaken tests** (20 failures) - Expected, weak references not supported in Java GC
2. **croak-locations.t** (29 failures) - Carp reports `(eval N)` instead of actual filename
3. **demolish tests** (6 failures) - Expected, DESTROY not supported
-4. **moo-utils-_subname-Sub-Name.t** (1 failure) - Expected, we have Sub::Util (no fallback to Sub::Name)
-5. **no-moo.t** (5 failures) - Namespace cleanup requires weak references
-6. **overloaded-coderefs.t** - Expected, B::Deparse not available
-7. **Mo t/strict.t** (1 failure) - Error message format differs from Perl
+4. **no-moo.t** (5 failures) - Namespace cleanup requires weak references
+5. **overloaded-coderefs.t** - Expected, B::Deparse not available
+6. **Mo t/strict.t** (1 failure) - Error message format differs from Perl
**Expected failures** (not fixable without fundamental changes):
- Weak references: accessor-weaken tests (20), no-moo.t cleanup (5)
- DESTROY/GC: demolish tests (6)
- Missing B::Deparse: overloaded-coderefs.t
-- Sub::Name fallback: moo-utils-_subname-Sub-Name.t (1)
**Potentially fixable**:
- croak-locations.t (29) - Carp filename in string eval
@@ -646,6 +656,7 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas
- **Branch**: `feature/moo-support` (PR #319 - merged)
- **Branch**: `fix/goto-tailcall-import` (PR #320 - open)
- **Branch**: `fix/mo-bareword-parsing` (PR #322 - open)
+- **Branch**: `feature/sub-name` (PR #324 - open)
- **Key commits**:
- `00c124167` - Fix print { func() } filehandle block parsing and JVM codegen
- `393bedf0f` - Fix quotemeta and Package::SUPER::method resolution
@@ -655,6 +666,7 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas
- `db434f8d3` - Fix ::identifier bareword parsing and add cpan to sync
- `ff31163f9` - Fix self-referential hash assignment %h = (stuff, %h)
- `a3233cd55` - Improve ::identifier to check sub existence at compile time
+ - `86591c703` - Add Sub::Name module and fix @INC hook exception handling
## Related Documents
diff --git a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java
index da9841714..d0d8068e7 100644
--- a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java
+++ b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java
@@ -862,18 +862,15 @@ else if (hook.type == RuntimeScalarType.ARRAYREFERENCE && hook.value instanceof
args.push(selfArg);
args.push(new RuntimeScalar(fileName));
- try {
- RuntimeBase result = codeRef.apply(args, RuntimeContextType.SCALAR);
-
- // If result is undef, return null to continue to next @INC entry
- if (result == null || !result.scalar().defined().getBoolean()) {
- return null;
- }
+ // Call the hook - if it throws an exception, propagate it
+ // This matches Perl's behavior where die() in an @INC hook stops require
+ RuntimeBase result = codeRef.apply(args, RuntimeContextType.SCALAR);
- return result;
- } catch (Exception e) {
- // If hook throws an exception, continue to next @INC entry
+ // If result is undef, return null to continue to next @INC entry
+ if (result == null || !result.scalar().defined().getBoolean()) {
return null;
}
+
+ return result;
}
}
\ No newline at end of file
diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/SubName.java b/src/main/java/org/perlonjava/runtime/perlmodule/SubName.java
new file mode 100644
index 000000000..28aaa4993
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/perlmodule/SubName.java
@@ -0,0 +1,86 @@
+package org.perlonjava.runtime.perlmodule;
+
+import org.perlonjava.runtime.runtimetypes.*;
+
+import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.*;
+
+/**
+ * Sub::Name module implementation for PerlOnJava.
+ * Provides the subname() function to (re)name a subroutine.
+ *
+ * This is compatible with the CPAN Sub::Name module.
+ * The subname is only used for informative routines (caller, Carp, etc).
+ *
+ * @see Sub::Name on CPAN
+ */
+public class SubName extends PerlModuleBase {
+
+ /**
+ * Constructor for SubName.
+ */
+ public SubName() {
+ super("Sub::Name", false); // false because loaded via XSLoader
+ }
+
+ /**
+ * Static initializer called by XSLoader::load().
+ */
+ public static void initialize() {
+ SubName subName = new SubName();
+ subName.initializeExporter();
+ // Set $VERSION to match CPAN version
+ GlobalVariable.getGlobalVariable("Sub::Name::VERSION").set(new RuntimeScalar("0.28"));
+ // subname is exported by default
+ subName.defineExport("EXPORT", "subname");
+ subName.defineExport("EXPORT_OK", "subname");
+ try {
+ subName.registerMethod("subname", null); // No prototype to allow flexible args
+ } catch (NoSuchMethodException e) {
+ System.err.println("Warning: Missing Sub::Name method: " + e.getMessage());
+ }
+ }
+
+ /**
+ * subname NAME, CODEREF
+ *
+ * Assigns a new name to referenced sub. If package specification is omitted in
+ * the name, then the current package is used. The return value is the sub.
+ *
+ * The name is only used for informative routines (caller, Carp, etc). You won't
+ * be able to actually invoke the sub by the given name. To allow that, you need
+ * to do glob-assignment yourself.
+ *
+ * @param args The arguments: name string, CODE reference
+ * @param ctx The context
+ * @return The CODE reference
+ */
+ public static RuntimeList subname(RuntimeArray args, int ctx) {
+ if (args.size() != 2) {
+ throw new IllegalStateException("Usage: subname(name, coderef)");
+ }
+ RuntimeScalar nameScalar = args.get(0);
+ RuntimeScalar codeRef = args.get(1);
+
+ if (codeRef.type != CODE) {
+ throw new IllegalArgumentException("Not a subroutine reference");
+ }
+
+ RuntimeCode code = (RuntimeCode) codeRef.value;
+ String fullName = nameScalar.toString();
+
+ // Parse package::subname format
+ int lastColon = fullName.lastIndexOf("::");
+ if (lastColon >= 0) {
+ code.packageName = fullName.substring(0, lastColon);
+ code.subName = fullName.substring(lastColon + 2);
+ } else {
+ // No package specified - use current package from caller
+ RuntimeList callerInfo = RuntimeCode.caller(new RuntimeList(), RuntimeContextType.LIST);
+ if (!callerInfo.isEmpty()) {
+ code.packageName = callerInfo.elements.get(0).toString();
+ }
+ code.subName = fullName;
+ }
+ return codeRef.getList();
+ }
+}
diff --git a/src/main/perl/lib/Sub/Name.pm b/src/main/perl/lib/Sub/Name.pm
new file mode 100644
index 000000000..b49a744e9
--- /dev/null
+++ b/src/main/perl/lib/Sub/Name.pm
@@ -0,0 +1,80 @@
+package Sub::Name;
+# ABSTRACT: (Re)name a sub
+
+#
+# PerlOnJava implementation of Sub::Name
+# Original module by Matthijs van Duin
+#
+# The implementation is in:
+# src/main/java/org/perlonjava/runtime/perlmodule/SubName.java
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '0.28';
+
+use Exporter ();
+*import = \&Exporter::import;
+
+our @EXPORT = qw(subname);
+our @EXPORT_OK = @EXPORT;
+
+XSLoader::load('Sub::Name', $VERSION);
+
+1;
+
+__END__
+
+=head1 NAME
+
+Sub::Name - (Re)name a sub
+
+=head1 SYNOPSIS
+
+ use Sub::Name;
+
+ subname $name, $subref;
+
+ $subref = subname foo => sub { ... };
+
+=head1 DESCRIPTION
+
+This module has only one function, which is also exported by default:
+
+=head2 subname NAME, CODEREF
+
+Assigns a new name to referenced sub. If package specification is omitted in
+the name, then the current package is used. The return value is the sub.
+
+The name is only used for informative routines (caller, Carp, etc). You won't
+be able to actually invoke the sub by the given name. To allow that, you need
+to do glob-assignment yourself.
+
+Note that for anonymous closures (subs that reference lexicals declared outside
+the sub itself) you can name each instance of the closure differently, which
+can be very useful for debugging.
+
+=head1 SEE ALSO
+
+=over 4
+
+=item *
+
+L - for getting information about subs
+
+=item *
+
+L - set_subname is another implementation of C
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2004, 2008 by Matthijs van Duin, all rights reserved;
+copyright (c) 2014 cPanel Inc., all rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut