@@ -365,14 +365,69 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper
365365 }
366366 }
367367 if (node .left instanceof ListNode list ) { // ("a","b","c")[2]
368- // transform to: ["a","b","c"]->[2]
369- BinaryOperatorNode refNode = new BinaryOperatorNode ("->" ,
370- new ArrayLiteralNode (list .elements , list .getIndex ()),
371- node .right , node .tokenIndex );
372- refNode .accept (emitterVisitor );
368+ // Use proper list slice semantics: evaluate list, then slice
369+ // This differs from array dereference because empty list returns empty, not undef
370+ if (CompilerOptions .DEBUG_ENABLED ) emitterVisitor .ctx .logDebug ("visit(BinaryOperatorNode) (list)[indices] - list slice" );
371+
372+ // Evaluate the list
373+ list .accept (emitterVisitor .with (RuntimeContextType .LIST ));
374+
375+ // Convert to RuntimeList if not already (handles RuntimeScalar case)
376+ emitterVisitor .ctx .mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
377+ "org/perlonjava/runtime/runtimetypes/RuntimeBase" ,
378+ "getList" ,
379+ "()Lorg/perlonjava/runtime/runtimetypes/RuntimeList;" ,
380+ false );
381+
382+ // Save the list to a local variable before evaluating indices.
383+ // This is necessary because indices may contain function calls that
384+ // generate complex bytecode with exception handlers, and the JVM
385+ // verifier requires consistent stack heights at merge points.
386+ int listVar = emitterVisitor .ctx .symbolTable .allocateLocalVariable ();
387+ emitterVisitor .ctx .mv .visitVarInsn (Opcodes .ASTORE , listVar );
388+
389+ // Evaluate the indices
390+ ListNode indices = ((ArrayLiteralNode ) node .right ).asListNode ();
391+ indices .accept (emitterVisitor .with (RuntimeContextType .LIST ));
392+
393+ // Save indices to local variable too
394+ int indicesVar = emitterVisitor .ctx .symbolTable .allocateLocalVariable ();
395+ emitterVisitor .ctx .mv .visitVarInsn (Opcodes .ASTORE , indicesVar );
396+
397+ // Load list and indices back, call RuntimeList.getSlice(indices)
398+ emitterVisitor .ctx .mv .visitVarInsn (Opcodes .ALOAD , listVar );
399+ emitterVisitor .ctx .mv .visitVarInsn (Opcodes .ALOAD , indicesVar );
400+ emitterVisitor .ctx .mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
401+ "org/perlonjava/runtime/runtimetypes/RuntimeList" ,
402+ "getSlice" ,
403+ "(Lorg/perlonjava/runtime/runtimetypes/RuntimeList;)Lorg/perlonjava/runtime/runtimetypes/RuntimeList;" ,
404+ false );
405+
406+ // Handle context conversion
407+ if (emitterVisitor .ctx .contextType == RuntimeContextType .SCALAR ) {
408+ emitterVisitor .ctx .mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL , "org/perlonjava/runtime/runtimetypes/RuntimeList" ,
409+ "scalar" , "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;" , false );
410+ } else if (emitterVisitor .ctx .contextType == RuntimeContextType .VOID ) {
411+ emitterVisitor .ctx .mv .visitInsn (Opcodes .POP );
412+ }
373413 return ;
374414 }
375415
416+ // For function calls and other expressions: (func())[index]
417+ // We need to use list slice semantics to handle empty lists correctly.
418+ // However, this should NOT apply to chained dereferences like $matrix[1][0]
419+ // where the first [1] returns a scalar (array reference) and the second
420+ // [0] should dereference it.
421+ //
422+ // List slice semantics apply when:
423+ // 1. The left side is a ListNode (literal list) - handled above
424+ // 2. The left side is a parenthesized function call (wantarray context)
425+ //
426+ // For now, we use the old transformation to ->[] for non-ListNode cases,
427+ // as most cases are array dereferences, not list slices.
428+ // TODO: Properly detect when the left side is a list-returning expression
429+ // vs. a scalar-returning expression.
430+
376431 // default: call `->[]`
377432 BinaryOperatorNode refNode = new BinaryOperatorNode ("->" , node .left , node .right , node .tokenIndex );
378433 refNode .accept (emitterVisitor );
0 commit comments