Skip to content

fix(naming): honor local *PKG::__ANON__ = 'name' in caller()/Sub::Util#617

Merged
fglock merged 1 commit intomasterfrom
feature/anon-sub-local-anon-naming
Apr 29, 2026
Merged

fix(naming): honor local *PKG::__ANON__ = 'name' in caller()/Sub::Util#617
fglock merged 1 commit intomasterfrom
feature/anon-sub-local-anon-naming

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 29, 2026

Summary

Make caller() and Sub::Util::subname honor the
local *PKG::__ANON__ = 'name' idiom for naming anonymous subs.
This is used by SUPER.pm, Try::Tiny, namespace::clean, and several
Moose internals so that stack traces and SUPER-dispatch report a
meaningful name for anon subs.

Previously the idiom was silently lost and caller(0)[3] always
returned Pkg::__ANON__, which broke SUPER's t/bugs.t (3/6
failures) and consequently Test::MockModule (which couldn't
install) and any module that depends on it (e.g. DBIx::Retry).

Mechanism (see dev/modules/anon_sub_naming.md)

  • RuntimeGlob gains a nameOverride string. Glob-as-scalar
    assignment (*foo = $string) records the override on the
    current glob in globalIORefs (looked up by name via a new
    non-vivifying GlobalVariable.peekGlobalIO helper), so the
    override correctly follows the fresh RuntimeGlob that
    local *FOO swaps in, not the lvalue captured before local.
  • RuntimeCode.callerWithSub consults
    globalIORefs["Pkg::__ANON__"].nameOverride for any anon-sub
    frame (innermost via __SUB__, deeper frames via the stack-trace
    Pkg::__ANON__ marker). Sub::Name / set_subname's
    explicitlyRenamed path keeps winning, so a CV that was
    renamed via Sub::Name is unaffected by an outer local *__ANON__,
    matching real Perl.
  • SubUtil.subname uses the same lookup.

This is option A from the design discussion in
dev/modules/anon_sub_naming.md ("glob indirection"), implemented
in a narrow, low-risk form that does not change the SCALAR-slot
semantics of *foo = "string" for any other use case.

Test plan

  • make passes (full unit-test suite + build)
  • SUPER t/bugs.t: 0/6 → 6/6 passing
  • Test::MockModule installs cleanly; 103/103 tests pass
  • DBIx::Retry chain unblocked: was bailing out at
    "Test::MockModule not found" (0 subtests run); now runs
    17/17 subtests (one remaining failure is an unrelated
    DBD::ExampleP issue)
  • Sub::Name regression baseline diff is empty
    (dev/modules/anon_sub_naming_baseline.txt captured before
    the change)
  • 10/10 targeted comprehensive cases match real Perl:
    plain anon, override, post-local reset, Sub::Name+local
    interaction, Sub::Util::subname inside local,
    package-qualified, nested anon frames, caller(N) from
    helpers, multi-call independence

Files

  • src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java
    nameOverride field, write path
  • src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java
    peekGlobalIO helper
  • src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
    callerWithSub read path
  • src/main/java/org/perlonjava/runtime/perlmodule/SubUtil.java
    subname read path
  • dev/modules/anon_sub_naming.md — design doc
  • dev/modules/anon_sub_naming_baseline.txt — captured Sub::Name
    baseline (no diff after this change)

Generated with Devin

Anonymous subs now expose the dynamically-scoped name set by the
`local *__ANON__ = 'name'` idiom (used by SUPER.pm, Try::Tiny,
namespace::clean, several Moose internals) to caller() and
Sub::Util::subname.

Mechanism (see dev/modules/anon_sub_naming.md):

* RuntimeGlob gains a `nameOverride` string. Glob-as-scalar
  assignment (`*foo = $string`) records it on the *current* glob
  in globalIORefs (looked up by name via the new
  GlobalVariable.peekGlobalIO helper), so the override follows
  the fresh glob installed by `local *FOO` rather than the
  lvalue captured before `local`.
* RuntimeCode.callerWithSub consults
  globalIORefs["Pkg::__ANON__"].nameOverride for any anon-sub
  frame (innermost via __SUB__, deeper via the stack-trace
  "Pkg::__ANON__" record). Sub::Name / set_subname's
  explicitlyRenamed path keeps winning, so already-renamed CVs
  are unaffected.
* SubUtil.subname uses the same lookup.

Unblocks the SUPER → Test::MockModule → DBIx::Retry chain:

* SUPER t/bugs.t: 0/6 → 6/6.
* Test::MockModule installs cleanly, 103/103 tests pass.
* DBIx::Retry was bailing out at "Test::MockModule not found"
  (0 subtests run); now runs 17/17 subtests, with one remaining
  unrelated DBD::ExampleP failure.
* Sub::Name baseline behavior is byte-identical to before
  (regression baseline captured in
  dev/modules/anon_sub_naming_baseline.txt).
* `make` passes.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the feature/anon-sub-local-anon-naming branch from 58d865d to c3effde Compare April 29, 2026 11:32
@fglock fglock merged commit ff1da2b into master Apr 29, 2026
2 checks passed
@fglock fglock deleted the feature/anon-sub-local-anon-naming branch April 29, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant