From aa401362f02570786b9556d83247529759041082 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 8 Sep 2026 20:13:31 +0200 Subject: [PATCH 1/5] wip: snapshot before fixing issue #1308 scalar semantics Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex From 56a05c010d036163e78fc12478f65174e283ea71 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 8 Sep 2026 20:22:39 +0200 Subject: [PATCH 2/5] fix: preserve scalar semantics in Data::Dumper Useperl mode Match stock Data::Dumper's safe-decimal behavior when Test::Differences selects the pure-Perl renderer while retaining scalar-aware normal dumps. Cover #1308, #1262, #1260, and #1158 with permanent backend-parity tests. Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex --- src/main/perl/lib/Data/Dumper.pm | 7 ++- .../unit/scalar_dual_value_cpan_regressions.t | 58 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/unit/scalar_dual_value_cpan_regressions.t diff --git a/src/main/perl/lib/Data/Dumper.pm b/src/main/perl/lib/Data/Dumper.pm index 361b8d2d6d..a100f5582a 100644 --- a/src/main/perl/lib/Data/Dumper.pm +++ b/src/main/perl/lib/Data/Dumper.pm @@ -580,7 +580,12 @@ sub _dump { $out .= sprintf "v%vd", $val; } # \d here would treat "1\x{660}" as a safe decimal number - elsif (defined &Data::Dumper::_perlonjava_numified_safe_decimal + # Test::Differences explicitly selects Data::Dumper's pure-Perl renderer. + # That renderer has always used the safe-decimal fallback below, which + # renders numeric-looking strings without quotes. Keep that behavior in + # Useperl mode; otherwise use PerlOnJava's scalar-aware hook so an + # untouched string remains distinguishable from a numeric scalar. + elsif ((!$s->{useperl} && defined &Data::Dumper::_perlonjava_numified_safe_decimal) ? Data::Dumper::_perlonjava_numified_safe_decimal($val) : $val =~ /^(?:0|-?[1-9][0-9]{0,8})\z/) { $out .= $val; diff --git a/src/test/resources/unit/scalar_dual_value_cpan_regressions.t b/src/test/resources/unit/scalar_dual_value_cpan_regressions.t new file mode 100644 index 0000000000..c75eee6fa0 --- /dev/null +++ b/src/test/resources/unit/scalar_dual_value_cpan_regressions.t @@ -0,0 +1,58 @@ +use strict; +use warnings; +use Test::More; +use Test::Differences qw(eq_or_diff); +use Data::Dumper; +use JSON::PP qw(decode_json); +use B (); + +# Equivalent to List::PowerSet 0.01. Its recursive list copies must retain +# the scalar behavior Test::Differences observes with stock Perl. +sub powerset { + return [[]] if @_ == 0; + my $first = shift; + my $pow = powerset(@_); + return [ map { [$first, @$_], [@$_] } @$pow ]; +} + +eq_or_diff( + powerset(qw(1 2 3)), + [[1, 2, 3], [2, 3], [1, 3], [3], [1, 2], [2], [1], []], + 'recursive copies of numeric-looking qw values match numeric literals', +); + +# HTTP::BrowserDetect compares a computed numeric zero with a JSON string +# fixture through this same Data::Dumper-backed comparison path. +eq_or_diff(0, '0', 'numeric zero and a string zero compare through Test::Differences'); + +my $document = decode_json('{"number":1,"string":"1"}'); +my $number_flags = B::svref_2object(\$document->{number})->FLAGS; +my $string_flags = B::svref_2object(\$document->{string})->FLAGS; +ok($number_flags & (B::SVp_IOK() | B::SVp_NOK()), + 'JSON numeric token has numeric scalar flags'); +ok(!($number_flags & B::SVp_POK()), + 'JSON numeric token does not have string-only flags'); +ok($string_flags & B::SVp_POK(), + 'JSON string token has string flags'); +ok(!($string_flags & (B::SVp_IOK() | B::SVp_NOK())), + 'JSON string token does not gain numeric flags'); + +sub abs2rel { + return if !@_; + my @result = $_[0]; + for my $i (1 .. $#_) { + push @result, $_[$i] - $_[$i - 1]; + } + return @result; +} + +eq_or_diff( + [abs2rel(qw(1 2 3))], + [qw(1 1 1)], + 'arithmetic on string inputs preserves the comparison-visible string channel', +); + +local $Data::Dumper::Terse = 1; +is(Dumper('nonnumeric'), "'nonnumeric'\n", 'ordinary nonnumeric strings remain strings'); + +done_testing; From 96b4721dc1067d2ef86d98bb7e8479df9dfd253d Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 8 Sep 2026 20:23:46 +0200 Subject: [PATCH 3/5] docs: record scalar compatibility correction Document the #1308 scalar compatibility fix in the unreleased changelog. Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex --- docs/about/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 7cd081e54b..6406c88af2 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -6,6 +6,9 @@ priorities and future plans. ## Work in progress +- Preserve Data::Dumper's pure-Perl numeric-string behavior for + Test::Differences, including copied `qw` values and numeric zero fixtures. + - Correct named-unary operand precedence, so `! scalar @array % 2` evaluates the modulo operation before its logical negation. From 6905af35b519082b5f77fcb6f2ca56ba7a5d0059 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 8 Sep 2026 20:28:44 +0200 Subject: [PATCH 4/5] test: cover scalar CPAN fixture provenance Exercise the numeric-zero comparison through a pure-Perl method and a decoded JSON fixture, matching the HTTP::BrowserDetect regression shape. Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex --- src/test/resources/unit/scalar_dual_value_cpan_regressions.t | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/resources/unit/scalar_dual_value_cpan_regressions.t b/src/test/resources/unit/scalar_dual_value_cpan_regressions.t index c75eee6fa0..be0f95a903 100644 --- a/src/test/resources/unit/scalar_dual_value_cpan_regressions.t +++ b/src/test/resources/unit/scalar_dual_value_cpan_regressions.t @@ -23,7 +23,10 @@ eq_or_diff( # HTTP::BrowserDetect compares a computed numeric zero with a JSON string # fixture through this same Data::Dumper-backed comparison path. -eq_or_diff(0, '0', 'numeric zero and a string zero compare through Test::Differences'); +sub browser_major { return 0 } +my $browser_fixture = decode_json('{"browser_major":"0"}'); +eq_or_diff(browser_major(), $browser_fixture->{browser_major}, + 'method-returned numeric zero and JSON string zero compare through Test::Differences'); my $document = decode_json('{"number":1,"string":"1"}'); my $number_flags = B::svref_2object(\$document->{number})->FLAGS; From 65555c3f30077b461441f4fb9afa48eb8daa5b98 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 8 Sep 2026 21:04:06 +0200 Subject: [PATCH 5/5] test: remove undeclared CI dependency from scalar regression Embed Test::Differences' Data::Dumper configuration in the project-owned test so clean CI images exercise the compatibility path without CPAN installation. Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex --- .../unit/scalar_dual_value_cpan_regressions.t | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/test/resources/unit/scalar_dual_value_cpan_regressions.t b/src/test/resources/unit/scalar_dual_value_cpan_regressions.t index be0f95a903..361e50c124 100644 --- a/src/test/resources/unit/scalar_dual_value_cpan_regressions.t +++ b/src/test/resources/unit/scalar_dual_value_cpan_regressions.t @@ -1,11 +1,29 @@ use strict; use warnings; use Test::More; -use Test::Differences qw(eq_or_diff); use Data::Dumper; use JSON::PP qw(decode_json); use B (); +sub eq_or_diff_via_data_dumper { + my ($got, $expected, $name) = @_; + + # Test::Differences enables this pure-Perl Data::Dumper configuration + # before comparing its serialized values. Keep the project unit test + # self-contained: Test::Differences is a CPAN test dependency and is not + # installed by the clean CI image. + local $Data::Dumper::Deparse = 1; + local $Data::Dumper::Indent = 1; + local $Data::Dumper::Purity = 0; + local $Data::Dumper::Terse = 1; + local $Data::Dumper::Deepcopy = 1; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Useperl = 1; + local $Data::Dumper::Sortkeys = 1; + + is(Dumper($got), Dumper($expected), $name); +} + # Equivalent to List::PowerSet 0.01. Its recursive list copies must retain # the scalar behavior Test::Differences observes with stock Perl. sub powerset { @@ -15,7 +33,7 @@ sub powerset { return [ map { [$first, @$_], [@$_] } @$pow ]; } -eq_or_diff( +eq_or_diff_via_data_dumper( powerset(qw(1 2 3)), [[1, 2, 3], [2, 3], [1, 3], [3], [1, 2], [2], [1], []], 'recursive copies of numeric-looking qw values match numeric literals', @@ -25,7 +43,7 @@ eq_or_diff( # fixture through this same Data::Dumper-backed comparison path. sub browser_major { return 0 } my $browser_fixture = decode_json('{"browser_major":"0"}'); -eq_or_diff(browser_major(), $browser_fixture->{browser_major}, +eq_or_diff_via_data_dumper(browser_major(), $browser_fixture->{browser_major}, 'method-returned numeric zero and JSON string zero compare through Test::Differences'); my $document = decode_json('{"number":1,"string":"1"}'); @@ -49,7 +67,7 @@ sub abs2rel { return @result; } -eq_or_diff( +eq_or_diff_via_data_dumper( [abs2rel(qw(1 2 3))], [qw(1 1 1)], 'arithmetic on string inputs preserves the comparison-visible string channel',