Skip to content

Fix caller() and support local with method chain lvalues in interpreter#306

Merged
fglock merged 2 commits into
masterfrom
feature/local-method-chain
Mar 13, 2026
Merged

Fix caller() and support local with method chain lvalues in interpreter#306
fglock merged 2 commits into
masterfrom
feature/local-method-chain

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Mar 13, 2026

Summary

This PR contains two related fixes for the bytecode interpreter:

Fix 1: caller() returning incorrect frames

The interpreter's caller() function was returning incorrect stack frames, breaking Exporter and causing all ExifTool tests to fail with "Symbol not allowed for export" errors.

Changes:

  • Use InterpretedCode.apply as the boundary marker for Perl call levels
  • Track addedFrameForCurrentLevel flag to add only one interpreter frame per call level
  • For innermost frame (index 0), use runtime currentPackage to reflect package Foo; declarations

Fix 2: Support local $obj->method->{key} syntax

Extend interpreter to handle localizing hash/array elements accessed through method chains.

Changes:

  • BytecodeCompiler.java: Accept any BinaryOperatorNode as a general fallback for local operands
  • CompileAssignment.java: Handle any BinaryOperatorNode for local assignment expressions

Both changes align with the JVM backend's behavior in EmitOperatorLocal.java.

Test Plan

  • ./gradlew test passes
  • ExifTool tests pass (ExifTool.t: 35/35, Writer.t: 61/61)
  • use Test::More works in interpreter mode
  • local $obj->hub->{ended} = 99 works correctly

Generated with Devin

@fglock fglock force-pushed the feature/local-method-chain branch from 5673ea9 to 674e132 Compare March 13, 2026 12:37
The interpreter's caller() function was returning incorrect stack frames
in two scenarios:

1. For regular subroutine calls (main calls outer() calls inner()),
   caller() returned no frames because all BytecodeInterpreter.execute
   frames were consecutive and the previous fix treated them as one call.

2. For use/require with import(), the stack order was wrong - the
   CallerStack entry (from parseUseDeclaration) was being added after
   interpreter frames instead of at the correct position.

Fix: Use InterpretedCode.apply as the boundary marker. Each apply() call
marks the END of a Perl subroutine execution. Multiple execute() frames
within an apply() share one interpreter frame.

Key changes to ExceptionFormatter.formatException():
- Track addedFrameForCurrentLevel flag
- Only add one interpreter frame per call level
- When we see InterpretedCode.apply, increment to next frame index
- For innermost frame (index 0), use runtime currentPackage to reflect
  package declarations that executed at runtime

Now both test cases work correctly:
- Regular sub calls: caller(0)=main, caller(1)=main, depth=1
- use/import: caller(0)=NestedInstance, caller(1)=main

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

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock force-pushed the feature/local-method-chain branch from 674e132 to 01eb2c6 Compare March 13, 2026 12:59
Extend interpreter to handle `local $obj->method->{key}` syntax:

- BytecodeCompiler.java: Accept any BinaryOperatorNode as a general
  fallback for local operands, matching JVM backend behavior
- CompileAssignment.java: Handle any BinaryOperatorNode for local
  assignment expressions like `local $obj->method->{key} = value`

Both changes align with EmitOperatorLocal.java approach: compile
the lvalue expression and call pushLocalVariable on the result.

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

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock force-pushed the feature/local-method-chain branch from 01eb2c6 to 5506fe2 Compare March 13, 2026 13:01
@fglock fglock changed the title Support local with method chain lvalues in interpreter Fix caller() and support local with method chain lvalues in interpreter Mar 13, 2026
@fglock fglock merged commit 65b2d44 into master Mar 13, 2026
2 checks passed
@fglock fglock deleted the feature/local-method-chain branch March 13, 2026 13:12
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