From 9f4dd82645f62428ccab90e413541f46d6179241 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 25 Feb 2026 10:31:08 +0100 Subject: [PATCH] BytecodeCompiler: fix wantarray in eval STRING void context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VOID-to-SCALAR promotion in BytecodeCompiler.compile() was wrong. It caused wantarray() inside an eval STRING called in void context to return false (scalar) instead of undef (void). Fix: remove the promotion entirely, passing the true outer context unchanged — exactly as the JVM compiler does in EmitEval.java (emitterVisitor.ctx.contextType passed as-is to evalStringWithInterpreter). Result: op/eval.t now passes 153/153 in both JVM and interpreter modes. --- .../backend/bytecode/BytecodeCompiler.java | 17 +++++++++-------- .../backend/bytecode/CompileOperator.java | 9 +++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 3469f845da..d5ba0b7564 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -90,6 +90,10 @@ private static class LoopInfo { // Track current calling context for subroutine calls int currentCallContext = RuntimeContextType.LIST; // Default to LIST + // The original caller context before eval STRING body promotion (VOID→SCALAR). + // wantarray inside eval STRING must reflect the TRUE outer context, not the promoted one. + int outerCallContext = RuntimeContextType.LIST; + // Closure support private RuntimeBase[] capturedVars; // Captured variable values private String[] capturedVarNames; // Parallel array of names @@ -473,14 +477,11 @@ public InterpretedCode compile(Node node, EmitterContext ctx) { // Detect closure variables if context is provided if (ctx != null) { detectClosureVariables(node, ctx); - // Use the calling context from EmitterContext for top-level expressions. - // Exception: eval STRING body always produces the value of its last expression, - // even when the caller uses it in void context. Compiling the body in VOID - // context would discard the result (e.g. `INIT { eval '1' or die }` would - // fail because eval returns undef). Use SCALAR as the minimum context. - currentCallContext = (ctx.contextType == RuntimeContextType.VOID) - ? RuntimeContextType.SCALAR - : ctx.contextType; + // Use the calling context from EmitterContext, exactly as the JVM compiler does. + // The true outer context is passed through unchanged so wantarray inside the + // eval body reflects the real call site context. + outerCallContext = ctx.contextType; + currentCallContext = ctx.contextType; // Sync package, pragmas, warnings, and features from ctx.symbolTable. // ctx.symbolTable is the compile-time scope snapshot at the eval call site — diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 5cb24fcc61..9633387346 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -849,11 +849,16 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // exactly as the JVM compiler does via EmitEval/evalTag. // The evalTag is unique per eval site and baked into the string pool. String evalTag = "interp_eval_" + EVAL_TAG_COUNTER.incrementAndGet(); + // currentCallContext is the TRUE outer context (e.g. VOID for a statement eval). + // This is what wantarray inside the eval body must see. + // The VOID→SCALAR promotion in BytecodeCompiler.compile() affects only how the + // eval BODY's last expression is compiled, not the runtime call context. + int outerCtx = bytecodeCompiler.currentCallContext; EmitterContext snapCtx = new EmitterContext( new JavaClassInfo(), bytecodeCompiler.symbolTable.snapShot(), // compile-time scope snapshot null, null, - bytecodeCompiler.currentCallContext, + outerCtx, false, null, new CompilerOptions(), @@ -864,7 +869,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); - bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + bytecodeCompiler.emit(outerCtx); bytecodeCompiler.emit(tagIdx); bytecodeCompiler.lastResultReg = rd;