Skip to content

feat: Type::Tiny support — investigation and fixes#475

Open
fglock wants to merge 18 commits intomasterfrom
feature/type-tiny-support
Open

feat: Type::Tiny support — investigation and fixes#475
fglock wants to merge 18 commits intomasterfrom
feature/type-tiny-support

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 9, 2026

Summary

Investigate and fix ./jcpan --jobs 8 -t Type::Tiny test failures for Type::Tiny 2.010001.

  • Add dev/modules/type_tiny.md investigation plan with 14 categorized issues
  • Fix looks_like_number() to parse string values (Phase 1)
  • Fix my scoping in for statement modifier (Phase 2)
  • Fix prototype ;$ with | infix operator parsing (Phase 3)

Test plan

  • make passes (no regressions)
  • ./jcpan --jobs 8 -t Type::Tiny shows improved pass rate
  • looks_like_number('1.1') returns true
  • for (my ($s, @a) = @_) scopes variables to enclosing block
  • ArrayRef | HashRef parses correctly with ;$ prototype

Generated with Devin

fglock and others added 9 commits April 9, 2026 22:21
Add dev/modules/type_tiny.md documenting the investigation of
jcpan Type::Tiny test failures, categorizing 14 distinct issues
by root cause and priority.

Key findings:
- looks_like_number() only checks internal type, not string content
- my declarations in for statement modifier dont leak to outer scope
- Prototype ;$ functions followed by | infix cause syntax errors
- Type::Params v2 wrap_subs cant find functions in callers stash

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
'for (my ($s, @A) = @_)' with outer parentheses caused the modifier
expression to be wrapped in a ListNode, which prevented the existing
my-hoisting logic from matching the BinaryOperatorNode pattern.

Now unwraps single-element ListNode before checking for the assignment
pattern, so both 'for my @w = LIST' and 'for (my @w = LIST)' correctly
hoist variable declarations to the enclosing scope.

This fixes the dominant Type::Tiny failure (~40+ tests) caused by
Type::Tiny.pm line 610: 'for (my ($s, @A) = @_)' where $s was
not visible on the next line.

Part of Type::Tiny support (Phase 2).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Functions with ;$ prototype (e.g. ArrayRef, HashRef from Types::Standard)
now correctly parse binary-only infix operators as argument terminators.
For example, ArrayRef | HashRef is now parsed as (ArrayRef()) | (HashRef())
instead of trying to parse | HashRef as an argument to ArrayRef.

Added |, ^, |., ^., &., ==, !=, >, >=, .., ..., =~, !~, and ? to
isArgumentTerminator in PrototypeArgs.java. This matches Perl behavior
where these operators cannot start a primary expression.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When a subroutine contains goto $variable, PerlOnJava falls back to
the interpreter backend for that sub. Previously, caller() in such
subs returned the wrong package because ExceptionFormatter consumed
CallerStack entries from compile-time contexts (BEGIN/use) for
interpreter frames.

Fix: Remove CallerStack usage from interpreter frame processing
entirely. Interpreter frames now always use tokenIndex/PC-based
lookup via ByteCodeSourceMapper, avoiding contamination from
compile-time CallerStack entries. Also remove unused pre-scan code
and all DEBUG_CALLER instrumentation.

This fixes Type::Params::signature_for which uses goto $compiled
and needs correct caller() to find functions in the caller stash.

Tests now passing: v2-defaults.t (2/2), v2-positional.t (13/13),
v2-named.t (15/15), v2-allowdash.t (20/20), v2-listtonamed.t (17/17)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add instruction in AGENTS.md to write commit messages to a temp file
and use `git commit -F` instead of `-m`. This avoids problems with
apostrophes, backticks, and other special characters in commit messages.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Perl allows ${^_TYPE_PARAMS_MULTISIG} and similar variables where the
first character after ^ is underscore. The parser only accepted
uppercase letters (A-Z), causing compilation failures for code using
this syntax.

This fixes Type::Params multisig support which uses ${^_TYPE_PARAMS_MULTISIG}
internally to track which signature alternative matched.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Compiler infrastructure frames (e.g., anonymous sub wrappers from
runSpecialBlock for use/BEGIN argument evaluation) can have JVM line
number -1, producing stack frames with empty package and line -1.

Skip these invalid frames so caller() returns the correct package
in compile-time contexts like use constant FBB => enum(...).

This fixes Type::Tiny::Enum and other modules that call caller()
through wrapper functions during compile-time evaluation.

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

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

- BitwiseOperators: check for overloaded '(~' on blessed objects in
  bitwiseNot() and integerBitwiseNot() before performing bitwise NOT
- PrototypeArgs: treat ;$ and ;_ prototypes as named unary operators
  so that expressions like ArrayRef[Int] | HashRef[Int] parse correctly
  (| terminates the first argument instead of being consumed by it)

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

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

1. PrototypeArgs: recognize comma as "no argument" for named-unary
   operators with ;$ or ;_ prototypes. Previously, `Foo, bar` with
   sub Foo (;$) caused a syntax error because the comma wasn't
   treated as an argument terminator. This fixes Type::Params::Parameter
   loading (syntax error at line 445) and unblocks many Type::Tiny tests.

2. BytecodeInterpreter: EQ_STR and NE_STR opcodes now call
   CompareOperators.eq()/ne() instead of CompareOperators.cmp().
   The old code only checked the (cmp overload, missing the (eq/(ne
   overloads. This fixes matchfor.t where objects with overloaded eq
   were compared as plain strings.

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-support branch from 0fb24a8 to a379cb5 Compare April 9, 2026 21:14
fglock and others added 9 commits April 9, 2026 23:36
…umber

Two fixes:

1. Bytecode compiler (eval context) now compiles the right operand of
   grep/map/sort/all/any in LIST context, matching the JVM backend.
   Previously, the outer scalar context propagated to the list operand,
   causing `scalar(grep { ... } @array)` to only see the array count
   (a single number) instead of the actual array elements.

   Also simplified the ARRAY_SIZE opcode to use operand.scalar()
   uniformly, since RuntimeArray.scalar() already returns the count.

2. looks_like_number now recognizes signed Inf/NaN strings (-Inf, +Inf,
   -NaN, +NaN) and case-insensitive variants (inf, nan, etc.).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Updated dev/modules/type_tiny.md with:
- Phase 5a/5b completed phases and fixes
- Results history table (baseline → 99.0%)
- Remaining 10 failing tests analysis
- Next steps

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Three fixes that improve Type::Tiny pass rate from 99.1% to 99.3%:

1. Numify overload: Overload.numify() can return a STRING (e.g., "3.1"
   from a 0+ handler). getNumberLarge() now converts string results to
   proper numeric types. Fixes 0+$obj returning truncated integer.

2. AUTOLOAD $AUTOLOAD persistence: autoloadVariableName was permanently
   set on RuntimeCode objects after first AUTOLOAD fallback. This caused
   $self->SUPER::AUTOLOAD(@_) to overwrite $AUTOLOAD with garbage.
   Fixed by skipping $AUTOLOAD assignment when method name IS "AUTOLOAD".

3. Interpreter grep/map outer @_: executeGrep/executeMap didn't pass
   the enclosing sub's @_ to grep/map blocks (register 1), unlike the
   JVM backend. Dict+Slurpy inline checks use $_[0] inside grep blocks.

Tests fixed: structured.t (110→115/115), Bitfield/basic.t (80→81/81),
ConstrainedObject/basic.t (24→27/27), multisig-custom-message.t (→21/21)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When compiling `local @_ = @_`, the RHS @_ evaluated to register 1.
PUSH_LOCAL_VARIABLE then cleared register 1 before ARRAY_SET_FROM_LIST
could read from it, resulting in @_ always being empty after localizing.

Fix: when valueReg == regIdx, copy RHS to a temporary register via
NEW_ARRAY + ARRAY_SET_FROM_LIST before calling PUSH_LOCAL_VARIABLE.

The JVM backend is not affected (already clones the RHS list before
localizing).

This fix unlocks 246 more Type::Tiny tests that were previously dying
in Type::Params::Alternatives multisig dispatch. v2-returns.t now
passes all 5/5 subtests.

Type::Tiny results: 345/375 files, 3130/3166 subtests (98.9%)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two fixes for Type::Tiny test failures:

1. our ($a, $b) in eval STRING not shadowing outer lexicals:
   The list form of 'our' in eval STRING reused the captured register
   from outer 'my' variables without rebinding to the package global.
   Added LOAD_GLOBAL_SCALAR/ARRAY/HASH emission in the list 'our' path
   to match the single-variable 'our' handling.
   Fixes: Type-Tiny-Enum/sorter.t (0/1 → 1/1),
          Type-Tiny/list-methods.t (0/2 → 2/2)

2. Empty prototype parser syntax error on ',=> Int':
   For subs with empty prototype '()', parsePrototypeArguments was still
   called and incorrectly checked for consecutive commas in the outer
   context (e.g., 'Num ,=> Int' parsed as consecutive commas inside
   the Num() call). Added early return for empty prototypes.
   Fixes: Type-Tiny-Intersection/cmp.t (0/0 → 17/17)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When `eval { local @_ = @_ }` was used before `goto $coderef`, the
goto would pass the original @_ values instead of the modified ones.

Root cause: the interpreter's eval {} block did not restore `local`
variables at block exit. The function-level finally block eventually
restored them, but this was too late for `goto` because the TAILCALL
marker held a reference to the RuntimeArray, and the finally block's
restoration modified the array contents through that shared reference.

Fix: add evalLocalLevelStack to track DynamicVariableManager level at
EVAL_TRY entry. At EVAL_END (normal exit) and in all catch handlers,
pop the stack and restore to the saved level. This ensures `local`
variables inside eval {} are properly scoped to the eval block, matching
Perl 5 semantics.

This fixes the remaining 5 subtests in Type::Tiny's multisig-gotonext.t
which use `eval { local @_ = @_ }` followed by `goto $next`.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phase 5e fixed 5 more test files:
- sorter.t (0/1 → 1/1): our ($a,$b) in eval STRING
- list-methods.t (0/2 → 2/2): our ($a,$b) in eval STRING
- Intersection/cmp.t (0/0 → 17/17): empty prototype parser
- multisig-gotonext.t (1/6 → 8/8): eval local restore
- 00-begin.t (0/0 → 1/1): eval local restore

Remaining: 6 files with actual failures (12 not-OK tests),
9 files with missing features, ~6 Moo/runner-flaky files.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…h, local @_ in eval, ref aliasing

- UNIVERSAL::VERSION: return undef for no-arg calls when $VERSION is undefined
- B::CV::XSUB: return 0 since PerlOnJava has no XSUBs
- READONLY_SCALAR method dispatch: unwrap READONLY_SCALAR in RuntimeCode.call()
  and callCached() before checking blessId for method resolution
- local @_ in eval: EmitOperatorLocal now detects @_ as always-lexical,
  matching EmitVariable.java where @_ is parameter slot 1
- Ref aliasing: implement \$var = $ref for lexical scalars in both JVM
  (EmitVariable.java) and interpreter (CompileAssignment.java) backends.
  Uses scalarDeref() + ASTORE for JVM, DEREF_SCALAR_STRICT + ALIAS for interpreter.

Test results:
- 00-begin.t: 1/1 pass (was failing)
- Type-Tiny-Enum/basic.t: 25/25 pass (was 17/25)
- Type-Params/v2-multi.t: 5/5 pass (was failing)
- Eval-TypeTiny/basic.t: 4/6 pass (remaining 2 are DESTROY/tie limitations)

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When eval STRING executed require/do, the evalRuntimeContext ThreadLocal
leaked into the compilation of required modules. This caused
SpecialBlockParser.runSpecialBlock to incorrectly alias local variables
in required modules (e.g., $caller in constant.pm) to the eval captured
variables when they shared the same name.

The fix saves and clears evalRuntimeContext in executePerlCode/
executePerlAST and restores it after compilation, so modules loaded via
require/do during eval STRING execution have isolated compilation
contexts.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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