Skip to content

Fix exiftool CLI and achieve 100% interpreter test parity#269

Merged
fglock merged 15 commits into
masterfrom
fix-exiftool-cli
Mar 5, 2026
Merged

Fix exiftool CLI and achieve 100% interpreter test parity#269
fglock merged 15 commits into
masterfrom
fix-exiftool-cli

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Mar 4, 2026

Summary

  • Fix exiftool CLI: readdir list context, stat _ ctime, suppress JNA warnings
  • Prevent JVM emitter from permanently mutating the AST during code generation
  • Fix eval STRING pragma inheritance in interpreter mode
  • Fix interpreter x-operator context, length unicode, array exists/delete
  • Fix interpreter SCALAR_TO_LIST for PerlRange and reverse context
  • Fix eval STRING package context in INIT/END blocks — interpreter now saves/restores InterpreterState.currentPackage
  • Fix definedGlobalCodeRefAsScalar to recognize InterpretedCode via runtimeCode.defined()
  • Fix ARRAY_GET on RuntimeList to flatten aggregates before indexing — my ($a) = @_ in anonymous subs was returning array count instead of first element

Test results

  • Interpreter unit tests: 156/156 (100%) — up from 99/156 (63%)
  • Perl5 core tests: 93.2% pass rate (no regressions)
  • make: passes clean

Test plan

  • make passes
  • make test-interpreter — 156/156 (100%)
  • make test-all — no regressions

Generated with Devin

fglock and others added 11 commits March 4, 2026 20:59
- readdir in list context returned scalar count instead of file list,
  breaking recursive directory scanning (-r option)
- stat _ after file test ops returned mtime as ctime because native
  stat fields were not preserved; now statForFileTest calls nativeStat
- Remove AST split retry loop that caused infinite retries on large
  scripts like exiftool (8000+ lines)
- Restore CWD in glob.t to prevent cascading test failures

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

Co-Authored-By: Devin <noreply@cognition.ai>
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
- REPEAT opcode now passes actual context (LIST vs SCALAR) instead of
  hardcoding SCALAR, fixing (list) x N producing string repetition
- length() uses codePointCount() instead of String.length() for proper
  Unicode character counting
- Add ARRAY_EXISTS and ARRAY_DELETE opcodes with compiler fast paths,
  fixing "exists $array[idx]" and "delete $array[idx]" which crashed
  with "slow path not yet implemented"

Test impact: re/pat.t 219->947 ok, op/pack.t 10773->14567 ok

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

Co-Authored-By: Devin <noreply@cognition.ai>
The interpreter eval STRING path used a single set of strict/feature
flags from the InterpretedCode object, reflecting state at end of
compilation. eval STRING inside no strict refs would still inherit
the outer use strict. Now snapshots per-eval-site pragma flags at
compile time and passes them to EvalStringHandler.

Test impact: re/pat.t 947->1043 ok (master: 1055, delta now -12)

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

Co-Authored-By: Devin <noreply@cognition.ai>
- EmitOperator.handleSystemBuiltin: wrap elements.addFirst(handle) in
  try/finally to restore on exit
- HashLiteralNode/ArrayLiteralNode.asListNode(): copy elements list so
  Dereference autoquoting doesn't mutate the original AST
- EmitOperatorDeleteExists: save/restore unary + unwrapping

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

Co-Authored-By: Devin <noreply@cognition.ai>
- SCALAR_TO_LIST now preserves aggregate types (PerlRange, RuntimeArray)
  instead of scalarizing them, matching JVM backend behavior where
  consumers like Pack.pack() iterate via RuntimeList iterator
- reverse operator now passes actual calling context instead of
  hardcoded LIST, enabling scalar-context string reversal

Test impact: pack.t interpreter now matches JVM backend exactly (0 delta)

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

Co-Authored-By: Devin <noreply@cognition.ai>
The AST splitting retry was accidentally removed in 44df585. Without it,
large eval blocks (like those in pack.t) hit MethodTooLargeException and
fall through incorrectly, causing thousands of test failures.

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

Co-Authored-By: Devin <noreply@cognition.ai>
Anonymous InterpretedCode (INIT/END blocks, closures) had null packageName,
causing eval STRING inside them to compile in 'main' instead of the
module's package. Three changes:

1. InterpretedCode constructor: set packageName from compilePackage
2. BytecodeCompiler: set packageName on anonymous sub InterpretedCode
3. BytecodeInterpreter: save/restore InterpreterState.currentPackage
   at subroutine boundaries so eval STRING inherits correct package

This fixes Test2::API's INIT { eval 'END { test2_set_is_end() }' }
which was failing with "Undefined subroutine &main::test2_set_is_end".

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

Co-Authored-By: Devin <noreply@cognition.ai>
definedGlobalCodeRefAsScalar checked only methodHandle/compilerSupplier/
isBuiltin, missing InterpretedCode subs (which override defined() to
return true). Use runtimeCode.defined() instead, matching the pattern
already used by existsGlobalCodeRefAsScalar.

This fixes "defined &Pkg::sub" returning false for interpreter-compiled
glob-assigned subs like *push = sub { ... } in Test2::API::Stack.

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

Co-Authored-By: Devin <noreply@cognition.ai>
ARRAY_GET on a RuntimeList containing aggregates (like RuntimeArray)
was returning the aggregate object itself instead of the scalar at
the given index. This caused `my ($a) = @_` in interpreter-compiled
anonymous subs to assign the array count instead of the first element.

Use flattenElements() to resolve aggregates into individual scalars
before indexing. This brings interpreter test pass rate to 100%.

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

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock changed the title Fix exiftool CLI: readdir list context, stat _ ctime, remove AST retry Fix exiftool CLI and achieve 100% interpreter test parity Mar 5, 2026
fglock and others added 4 commits March 5, 2026 10:06
… hash flattening

- Refactor all three list assignment cases in CompileAssignment to use
  new SET_FROM_LIST opcode (373) instead of per-element ARRAY_GET loops
- Fix BytecodeCompiler not inheriting pragma flags (strict, feature,
  warnings) from emitter context, which caused declared_refs feature
  to be lost in nested eval STRING (decl-refs.t 156→322)
- Fix RuntimeList.flattenElements() hash handling to emit key-value
  pairs instead of values only
- Revert SCALAR_TO_LIST back to wrapping (not flattening) to preserve
  aliasing semantics
- Add SET_FROM_LIST disassembly support in InterpretedCode

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

Co-Authored-By: Devin <noreply@cognition.ai>
…r fallback

- Fix my-variable shadowing in bytecode compiler: compile RHS before
  adding variable to scope so `my $x = $x` reads the outer $x
- Fix top-level compile() to use LIST instead of VOID context, preventing
  LOAD_UNDEF from overwriting block results (fixes use strict/overload
  with --interpreter)
- Catch Throwable (not just RuntimeException) in compileToExecutable
  fallback, and add "Too many arguments in method signature" to fallback
  check so ClassFormatError triggers interpreter fallback

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

Co-Authored-By: Devin <noreply@cognition.ai>
Two bugs fixed:
1. BytecodeCompiler: handleGeneralArrayAccess compiled the left side
   (e.g. (0,0,1,1)) in the callers context. In scalar context, the
   comma expression returned only the last value instead of creating a
   list. Fix: force LIST context for the base expression.

2. SlowOpcodeHandler: executeDerefArrayNonStrict only handled
   RuntimeArray and scalar types. When CREATE_LIST produced a
   RuntimeList, the handler fell through to scalar() which converted
   the list to a count. Fix: add RuntimeList case that converts to
   RuntimeArray.

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

Co-Authored-By: Devin <noreply@cognition.ai>
… strict

- Fix eval STRING void context regression: add isEvalString flag to
  BytecodeCompiler so the VOID→LIST context override in compile()
  only applies to special blocks (BEGIN/INIT/END), not eval STRING.
  The override was baking LIST context into CALL_SUB bytecodes,
  making wantarray() return true instead of undef inside
  void-context eval STRING.

- Fix DEREF_ARRAY strict-refs to handle RuntimeList from CREATE_LIST
  by converting to RuntimeArray (fixes (unpack(...))[0] pattern)

- Fix filehandle IdentifierNode in compileBinaryAsListOp() to emit
  LOAD_GLOB instead of visiting through normal strict-vars path
  (fixes "binmode BIN" with strict subs, restores pack.t from 0
  to 14658 passing tests)

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

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock force-pushed the fix-exiftool-cli branch from e499975 to 398ec76 Compare March 5, 2026 17:34
@fglock fglock merged commit 2fc5115 into master Mar 5, 2026
2 checks passed
@fglock fglock deleted the fix-exiftool-cli branch March 5, 2026 18:03
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