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