diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 6fe3d8dde5..82c18de71a 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -6,6 +6,8 @@ priorities and future plans. ## Work in progress +- Preserve the input line number when a borrowed anonymous filehandle alias leaves a JVM subroutine scope (#1933). + - Resolve glob-backed filehandles in `POSIX::isatty` without assuming the glob value is a `RuntimeIO` (#1932). - Add an initial CryptX Java XS bridge for binary/base64url helpers, functional digests, and HMAC using the existing JCA and Bouncy Castle providers (#1220). diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index b2eabcdf5b..d45c22c80e 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -5201,8 +5201,16 @@ public static void scopeExitCleanup(RuntimeScalar scalar) { RuntimeIO last = RuntimeIO.getLastAccessedHandle(); if ((glob.globName != null && glob.globName.equals(last.globName)) || (glob.IO != null && glob.IO.value == last)) { - RuntimeIO.setLastAccessedHandle(null); - RuntimeIO.setLastReadlineHandleName(null); + // A borrowed anonymous handle alias can leave scope while the + // caller still owns the same IO glob. Keep its $. context until + // that durable owner leaves; clearing on the borrow breaks + // getline/input_line_number calls inside wrapper subroutines. + boolean borrowedLiveIo = isUnstashedIoGlob(glob) + && !scalar.ioOwner && glob.ioHolderCount > 0; + if (!borrowedLiveIo) { + RuntimeIO.setLastAccessedHandle(null); + RuntimeIO.setLastReadlineHandleName(null); + } } } diff --git a/src/test/resources/unit/io_handle_input_line_number.t b/src/test/resources/unit/io_handle_input_line_number.t new file mode 100644 index 0000000000..b4c92e5f84 --- /dev/null +++ b/src/test/resources/unit/io_handle_input_line_number.t @@ -0,0 +1,21 @@ +use strict; +use warnings; +use Test::More; +use IO::Handle; + +my $input = "first\nsecond\n"; +open my $fh, '<', \$input or die "open scalar handle: $!"; + +is($fh->getline, "first\n", 'read the first line from a scalar handle'); +is($., 1, 'the global input line number follows getline'); +my $read_line_number = sub { $. }; +is($read_line_number->(), 1, + 'the global input line number is visible inside a subroutine'); +is($fh->input_line_number, 1, + 'input_line_number reports the current line after getline'); + +is($fh->getline, "second\n", 'read the second line from a scalar handle'); +is($fh->input_line_number, 2, + 'input_line_number advances with subsequent getline calls'); + +done_testing;