Skip to content

fix(runtime): values() in scalar context returns count, not last element#619

Closed
fglock wants to merge 1 commit intomasterfrom
fix/values-scalar-context
Closed

fix(runtime): values() in scalar context returns count, not last element#619
fglock wants to merge 1 commit intomasterfrom
fix/values-scalar-context

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 29, 2026

Summary

Fix values() losing its scalar-context count when the result passes through a sub return (or any list-collapse path).

In real Perl, scalar(values %hash) returns the number of values, just like scalar(keys %hash). PerlOnJava already did this for direct calls, but as soon as the result went through a sub return the scalar context degraded to "last list element" semantics:

my %h = (a=>10, b=>20, c=>30);
sub f { return values %h }
my $x = f();   # Before: 30   |   After: 3

Root cause

RuntimeHash.keys() set scalarContextSize on the returned RuntimeArray so the later list-to-scalar conversion yielded the count. RuntimeHash.values() and RuntimeArray.values() did not, so any list-collapse path (sub return, comma operator, etc.) treated the result as a plain list and applied last-element semantics.

Fix

  • RuntimeHash.values() sets scalarContextSize on the returned array for both the regular and TIED_HASH paths.
  • RuntimeArray.values() now returns a fresh RuntimeArray aliasing the elements (so for (values @a) { $_++ } still mutates @a) with scalarContextSize set on the new array, so the original @a's own scalar-context behaviour is not affected.

How this surfaced

Class::MOP::Class::get_all_attributes ends in return values %attrs. Every scalar $meta->get_all_attributes therefore returned a stringified Moose::Meta::Attribute=HASH(0x…) instead of a count, breaking 3 subtests in MooseX::Role::Parameterized's t/001-parameters.t (and any downstream code using the same pattern).

Test plan

  • make passes (full unit test suite)
  • prove src/test/resources/unit shows identical pass/fail set vs master (no regressions; same pre-existing failures only)
  • Direct scalar(values %h) still returns count
  • return values %h from a sub returns count in scalar context
  • for (values @a) { $_ *= 10 } still mutates @a (alias semantics preserved)
  • for (values %h) { $_ *= 10 } still mutates hash values
  • Targeted repro:
    $ ./jperl -e 'sub f{return values %{{a=>10,b=>20,c=>30}}} print scalar(f()),"\n"'
    3
    

Generated with Devin

Previously, `scalar(values %h)` worked when called directly, but
`return values %h` from a sub collapsed to the last value instead of
the count when the caller was in scalar context. Same for arrays.

Root cause: RuntimeHash.keys() set scalarContextSize on the returned
RuntimeArray so list-to-scalar conversion later yields the count.
RuntimeHash.values() and RuntimeArray.values() did not, so any
list-collapse path (sub return, comma operator) used last-element
semantics.

Fix:
- RuntimeHash.values() sets scalarContextSize on the returned array
  for both regular and TIED_HASH paths.
- RuntimeArray.values() returns a fresh array aliasing the elements
  (so `for (values @A) { $_++ }` still mutates @A) with
  scalarContextSize set, so the original @A's scalar-context
  behaviour is unaffected.

Surfaced by Moose's `Class::MOP::Class::get_all_attributes` ending in
`return values %attrs`, which made every
`scalar $meta->get_all_attributes` return a stringified Attribute
instead of a count, breaking 3 subtests in
MooseX::Role::Parameterized's t/001-parameters.t.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock
Copy link
Copy Markdown
Owner Author

fglock commented Apr 29, 2026

Superseded — commit moved to PR #618 (fix/walker-gate-property-based)

@fglock fglock closed this Apr 29, 2026
@fglock fglock deleted the fix/values-scalar-context branch April 29, 2026 11:55
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