Skip to content

fix: Type::Tiny Phase 6 — tie infrastructure, lexical sub parsing, splice context#481

Merged
fglock merged 12 commits intomasterfrom
feature/type-tiny-phase6
Apr 10, 2026
Merged

fix: Type::Tiny Phase 6 — tie infrastructure, lexical sub parsing, splice context#481
fglock merged 12 commits intomasterfrom
feature/type-tiny-phase6

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 10, 2026

Summary

Phase 6 of Type::Tiny support, improving the pass rate from 99.0% to 99.7% (3029/3038 individual tests).

Fixes

  • Splice list context in interpreter backend: Function calls in splice replacement positions now evaluated in LIST context (not SCALAR), matching the JVM backend. Previously only one value was inserted instead of the full list.
  • Lexical sub + statement modifier parsing: Lexical subs (my sub name) now recognized as function calls before statement modifier keywords (if/unless/while/until/for/foreach/when). Previously quuux if 1 with a lexical sub treated quuux as a bareword.
  • tie(my(@arr), ...) backslash prototype parsing: The \[$@%*] prototype now correctly handles parenthesized my declarations. Added unwrapMyListDeclaration() helper to unwrap the extra ListNode from my(@bar).
  • return @tied_array in eval STRING: materializeSpecialVarsInResult was iterating arr.elements directly (the empty ArrayList backing TieArray), bypassing FETCHSIZE/FETCH. Now dispatches through getList() for tied arrays and hashes.

Test improvements

Test Before After
Eval-TypeTiny/lexical-subs.t 11/12 12/12
Eval-TypeTiny/aliases-tie.t 6/11 10/11
Type-Tie/basic.t 1/2 3/3
Type-Tie/01basic.t 15/17 17/17
Types-Standard/tied.t 0/0 27/27
Type-Library/exportables.t 0/0 11/11
Type-Library/exportables-duplicated.t 0/1 1/1
Type-Tiny-Enum/basic.t 17/17 25/25
Moo/basic.t 4/5 5/5
Moo/coercion.t 9/19 19/19
Moo/exceptions.t 13/15 15/15
Moo/inflation.t 9/11 11/11
Moo/coercion-inlining-avoidance.t 0/0 14/14

Remaining 5 failing test files (all known limitations)

  • Error-TypeTiny-Assertion/basic.t (28/29): B::Deparse output
  • Eval-TypeTiny/basic.t (11/12): DESTROY not implemented
  • Eval-TypeTiny/aliases-tie.t (10/11): DESTROY not implemented
  • Type-Tie/06clone.t (3/6): Clone doesn't preserve tie magic
  • Type-Tie/06storable.t (3/6): Storable doesn't preserve tie magic

Test plan

  • make passes (all unit tests)
  • Full Type::Tiny test suite: 3029/3038 (99.7%)
  • All Moo integration tests pass

Generated with Devin

fglock and others added 6 commits April 10, 2026 14:56
Two fixes for Type::Tiny test improvements:

1. splice replacement values now evaluated in LIST context in interpreter
   backend. Previously, function calls in splice replacement positions
   got scalar context, causing only one value to be inserted instead of
   the full list. (fixes Type-Tie/01basic.t splice tests)

2. Lexical subs (my sub) now recognized as function calls before
   statement modifier keywords (if/unless/while/until/for/foreach/when).
   Previously, `quuux if 1` with a lexical sub treated `quuux` as a
   bareword because the parser's indirect method call heuristic saw
   `if` as an identifier. (fixes Eval-TypeTiny/lexical-subs.t test 6)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two fixes that unblock tie-based aliasing in Eval::TypeTiny:

1. Backslash prototype \[$@%*] now correctly handles my(@arr) and
   my(%hash) parenthesized declarations. Previously, the extra ListNode
   wrapper from parentheses caused tie() to see an unsupported variable
   type instead of an array/hash reference. Added unwrapMyListDeclaration
   helper in PrototypeArgs.java.

2. materializeSpecialVarsInResult now handles tied arrays and hashes.
   Previously, it iterated arr.elements directly (the empty ArrayList
   backing TieArray), bypassing FETCHSIZE/FETCH. Now dispatches through
   getList() for tied containers, which correctly calls tie methods.

Test improvements:
- aliases-tie.t: 6/11 -> 10/11 (remaining 1 is DESTROY)
- Type-Tie/basic.t: 1/2 -> 3/3
- Type-Tie/01basic.t: already 17/17

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phase 6 improvements:
- Splice list context in interpreter backend
- Lexical sub + statement modifier parsing
- tie(my(@arr),...) backslash prototype parsing
- return @tied_array in eval STRING context
- materializeSpecialVarsInResult tied array/hash support

Test results: 3029/3038 tests passing (99.7%), 5 files with real failures
(all due to DESTROY/B::Deparse/Clone limitations, not runtime bugs)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
All sprintf/printf warnings (Invalid conversion, Missing argument,
Redundant argument) were unconditionally emitted via WarnDie.warn().
Changed to WarnDie.warnWithCategory() with the printf category,
matching Perl 5 behavior where these warnings only fire under
use warnings or use warnings printf.

Also updated type_tiny.md with analysis of remaining jcpan issues:
- builtin::export_lexically: 2 tests (PerlOnJava reports 5.042)
- Math::BigFloat: 1 test (core module not bundled)
- sprintf %{ warning: cosmetic, now properly gated

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Three related fixes for `local` variable restoration:

1. JVM backend (EmitStatement.java): Add Local.localSetup/localTeardown
   wrapping For3Node (while/for loops) so `last` exits that jump past the
   body block's own cleanup still restore `local` variables.

2. JVM backend (EmitControlFlow.java): Route non-local last/next/redo
   through returnLabel instead of using direct ARETURN. This ensures
   the subroutine's localTeardown (popToLocalLevel) runs when `last LABEL`
   crosses subroutine boundaries (e.g. `sub skip { local $^W=0; last SKIP }`).

3. Bytecode interpreter (BytecodeCompiler.java): Add GET_LOCAL_LEVEL /
   POP_LOCAL_LEVEL wrapping For3Node for both bare blocks and while/for
   loops, matching the JVM backend fix.

Also fixes:
- Remove debug trace from WarnDie.java
- Fix spurious "Argument isn't numeric" warning in SprintfOperator when
  checking for Inf/NaN on invalid format specifiers with string arguments.

Test impact: op/sprintf2.t recovers 1 test (1651->1652), restoring the
pre-regression baseline.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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/type-tiny-phase6 branch from e9d1d9c to 3c034f9 Compare April 10, 2026 12:58
Phase 7 of Type::Tiny support: Clone/Storable tie preservation.

- Replace custom Clone::PP (77 lines) with CPAN Clone::PP 1.08
  which supports tied variables, clone_self/clone_init hooks,
  depth limiting, and circular reference detection.

- Create Java-based Clone module (Clone.java) as XS replacement
  that properly deep-clones tied hashes, arrays, and scalars by
  cloning the tie handler and re-tying the clone.

- Fix Storable::dclone to handle tied variables:
  - Detect TieHash/TieArray/TieScalar in deepClone()
  - Deep-clone the handler and copy data via FETCH/STORE
  - Add TIED_SCALAR case for tied scalar elements
  - Fix STORABLE_freeze/thaw to create correct ref type
    (ARRAY vs HASH) for the thaw object

- Add bundled tests:
  - Clone-PP: 7 tests from CPAN Clone::PP 1.08
  - Type-Tie: 9 tests from Type-Tiny 2.010001

Test impact: Type-Tie/06clone.t 3/6 → 6/6,
             Type-Tie/06storable.t 3/6 → 6/6

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/type-tiny-phase6 branch from ba65237 to 4ccba36 Compare April 10, 2026 13:49
fglock and others added 5 commits April 10, 2026 16:07
…slash

In Perl, \$hash{key} auto-vivifies the hash entry so that subsequent
\$hash{key} references point to the same scalar slot. PerlOnJava was
returning references to temporary proxy objects instead, causing each
\$hash{key} to create a different reference.

Fix by overriding createReference() in RuntimeBaseProxy to vivify the
entry first and return a reference to the actual lvalue. Also override
in RuntimeScalarReadOnly to avoid triggering the read-only vivify()
which would throw.

This fixes Clone-PP/t/dclone.t test 7 and improves reference identity
semantics for hash and array element references.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ct syntax

Two fixes:

1. The .= operator was incorrectly dropping the UTF-8 flag when the
   target was a BYTE_STRING. The setPreservingByteString logic (JVM
   backend) and executeStringConcatAssign (interpreter backend) would
   downgrade STRING back to BYTE_STRING whenever the result fit in
   Latin-1, even when the RHS was UTF-8. Now we only preserve
   BYTE_STRING when the concat result itself is BYTE_STRING (meaning
   both operands were non-UTF-8).

   This fixes all 27 utf8_handling.t tests for XML::Parser.

2. LWPExternEnt.pl used indirect object syntax (new LWP::UserAgent())
   which was misinterpreted as a function call since XML::Parser has
   its own sub new. Changed to direct method syntax (LWP::UserAgent->new()).

   This fixes external_ent.t and parament.t for XML::Parser.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When the current package defines a subroutine (e.g., sub new in
XML::Parser), new LWP::UserAgent() was incorrectly parsed as
new(LWP::UserAgent()) instead of LWP::UserAgent->new().

The parser rejection rule for indirect object syntax with qualified
names (::) was too aggressive - it rejected ALL qualified names when
the calling sub existed, regardless of whether the name was a known
package. Now it only rejects when the qualified name is NOT a known
package (isPackage != true), preserving correct behavior for:
- new LWP::UserAgent() -> indirect object (package is known)
- is MojoMonkeyTest::bar() -> function call (sub is known)

Also reverts the LWPExternEnt.pl workaround from the previous commit,
since the parser now handles the original indirect object syntax
correctly.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…vification

Move the createReference override from RuntimeBaseProxy (which affected
all proxy types including substr, vec, and tied lvalues) to only
RuntimeHashProxyEntry and RuntimeArrayProxyEntry where auto-vivification
on backslash-ref is actually needed.

The array proxy version safely checks for existing elements before
vivifying, to avoid overwriting tied or special elements that may
already exist at that index.

Also fix autovivification of tied scalars: when a tied scalar is
array/hash-dereferenced and FETCH returns undef, the auto-vivified
reference is now STOREd back to the tied variable (matching Perl
behavior). Previously the auto-vivified value was stored in a
temporary and lost.

This fixes all 6 test regressions from the original createReference
commit, and improves gmagic.t from 31/42 to 42/56 (+11 tests).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The split function was creating results with STRING type (UTF-8 flagged)
for all inputs because new RuntimeScalar(String) always sets STRING type.
The fixup code only converted results to BYTE_STRING when the input was
explicitly BYTE_STRING, but missed INTEGER, DOUBLE, UNDEF, etc.

Changed the condition from checking type == BYTE_STRING to type != STRING
so that split results are only UTF-8 flagged when the input string itself
was UTF-8 flagged. This matches Perl behavior.

This fixes ExifTool PNG.t test 3 where WriteInfo to a scalar reference
produced UTF-8 flagged binary data (via split in ConvInv propagating
UTF-8 flag from integer 8), causing Unknown file type on read-back.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit 2d89cb8 into master Apr 10, 2026
2 checks passed
@fglock fglock deleted the feature/type-tiny-phase6 branch April 10, 2026 17:21
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