From 3e143835ce0c1b63d84ab8e41d1aefef8c408fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Fri, 17 Apr 2026 23:56:57 -0600 Subject: [PATCH 1/4] fix: croak on trailing dash-option without CODE ref in import list Previously, `use Overload::FileCheck '-e' => sub { 1 }, '-f'` would silently discard the trailing `-f` option. Now it produces a clear error message indicating the missing CODE ref. Co-Authored-By: Claude Opus 4.6 --- lib/Overload/FileCheck.pm | 4 ++++ t/import-trailing-option.t | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 t/import-trailing-option.t diff --git a/lib/Overload/FileCheck.pm b/lib/Overload/FileCheck.pm index 295e03f..f43a3e4 100644 --- a/lib/Overload/FileCheck.pm +++ b/lib/Overload/FileCheck.pm @@ -196,6 +196,10 @@ sub import { } } + if ( defined $_next_check ) { + Carp::croak(qq[Missing CODE ref for mock '$_next_check' in import list]); + } + # callback the exporter logic return __PACKAGE__->export_to_level( 1, $class, @for_exporter ); } diff --git a/t/import-trailing-option.t b/t/import-trailing-option.t new file mode 100644 index 0000000..8490afa --- /dev/null +++ b/t/import-trailing-option.t @@ -0,0 +1,42 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test2::Bundle::Extended; +use Test2::Tools::Explain; + +# Load the module without calling import so we can test import() directly. +require Overload::FileCheck; + +# Verify that a trailing dash-option without a CODE ref in the import list +# produces a clear error instead of being silently discarded. + +like( + dies { + Overload::FileCheck->import( '-e' => sub { 1 }, '-f' ); + }, + qr/Missing CODE ref for mock '-f'/, + 'trailing dash-option without value croaks' +); + +# Clean up the -e mock from the partial import above (it succeeded before -f error). +Overload::FileCheck::unmock_all_file_checks(); + +like( + dies { + Overload::FileCheck->import('-z'); + }, + qr/Missing CODE ref for mock '-z'/, + 'single dash-option without value croaks' +); + +# Valid imports should still work fine. +ok( + lives { + Overload::FileCheck->import(':check'); + }, + 'exporter tag imports normally' +); + +done_testing; From 863a11c1ae82c12854c89dcd8c4a222761fc04be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Fri, 17 Apr 2026 23:57:18 -0600 Subject: [PATCH 2/4] refactor: avoid redundant stat callback in -B directory check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The -B handler in _check_from_stat() was calling `-d $f_or_fh` to detect directories, which dispatched through the full mock system and triggered a second stat callback. Use _check_mode_type() on the already-fetched @stat array instead — same result, zero extra calls. Co-Authored-By: Claude Opus 4.6 --- lib/Overload/FileCheck.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Overload/FileCheck.pm b/lib/Overload/FileCheck.pm index f43a3e4..de35a81 100644 --- a/lib/Overload/FileCheck.pm +++ b/lib/Overload/FileCheck.pm @@ -348,7 +348,9 @@ sub _check_from_stat { # Heuristic text/binary checks (use glob _ to pass the cached stat) T => sub { _xs_unmock_op($optype); _to_bool( scalar -T *_ ) }, # ASCII or UTF-8 text (heuristic) B => sub { # binary file (opposite of -T) - return CHECK_IS_TRUE if @stat && ( $stat[ST_MODE] & _S_IFMT ) == S_IFDIR; + # Check directory via mode bits directly instead of calling the + # mocked -d operator, which would trigger a redundant stat callback. + return CHECK_IS_TRUE if _check_mode_type( $stat[ST_MODE], S_IFDIR ) == CHECK_IS_TRUE; _xs_unmock_op($optype); return _to_bool( scalar -B *_ ); }, From b28fc2bb10357310db24118050b39c2affd033c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Thu, 23 Apr 2026 10:11:06 -0600 Subject: [PATCH 3/4] fix: restore @stat guard in -B handler to prevent undef warning Restored the `@stat &&` guard on line 353 that was dropped during the `-B` refactor. Without it, `$stat[ST_MODE]` is undef when stat returns `[]` (file not found), causing the "Use of uninitialized value in numeric eq" warning that failed `t/B-no-double-dispatch.t` (which uses `Test2::Plugin::NoWarnings`). --- lib/Overload/FileCheck.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Overload/FileCheck.pm b/lib/Overload/FileCheck.pm index de35a81..f1b6ab2 100644 --- a/lib/Overload/FileCheck.pm +++ b/lib/Overload/FileCheck.pm @@ -350,7 +350,7 @@ sub _check_from_stat { B => sub { # binary file (opposite of -T) # Check directory via mode bits directly instead of calling the # mocked -d operator, which would trigger a redundant stat callback. - return CHECK_IS_TRUE if _check_mode_type( $stat[ST_MODE], S_IFDIR ) == CHECK_IS_TRUE; + return CHECK_IS_TRUE if @stat && _check_mode_type( $stat[ST_MODE], S_IFDIR ) == CHECK_IS_TRUE; _xs_unmock_op($optype); return _to_bool( scalar -B *_ ); }, From ed4a2ffdfa4bd0565470b528dd6b924ae4a548f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Thu, 23 Apr 2026 10:12:02 -0600 Subject: [PATCH 4/4] fix: return CHECK_IS_NULL for -B/-T on non-existent mock files to prevent double-dispatch --- lib/Overload/FileCheck.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Overload/FileCheck.pm b/lib/Overload/FileCheck.pm index f1b6ab2..9372a60 100644 --- a/lib/Overload/FileCheck.pm +++ b/lib/Overload/FileCheck.pm @@ -346,11 +346,12 @@ sub _check_from_stat { k => sub { _xs_unmock_op($optype); _to_bool( scalar -k _ ) }, # sticky bit # Heuristic text/binary checks (use glob _ to pass the cached stat) - T => sub { _xs_unmock_op($optype); _to_bool( scalar -T *_ ) }, # ASCII or UTF-8 text (heuristic) + T => sub { return CHECK_IS_NULL unless @stat; _xs_unmock_op($optype); _to_bool( scalar -T *_ ) }, # ASCII or UTF-8 text (heuristic) B => sub { # binary file (opposite of -T) + return CHECK_IS_NULL unless @stat; # file not found # Check directory via mode bits directly instead of calling the # mocked -d operator, which would trigger a redundant stat callback. - return CHECK_IS_TRUE if @stat && _check_mode_type( $stat[ST_MODE], S_IFDIR ) == CHECK_IS_TRUE; + return CHECK_IS_TRUE if _check_mode_type( $stat[ST_MODE], S_IFDIR ) == CHECK_IS_TRUE; _xs_unmock_op($optype); return _to_bool( scalar -B *_ ); },