From 3de225fa886dcb5d9927ce5a42420796c286a35f Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Sat, 12 Sep 2026 23:11:31 +0200 Subject: [PATCH] fix: preserve modulus semantics under no overloading Use Perl's divisor-sign modulus rule in the no-overload arithmetic path. This restores dynamically compiled DateTime::Lite accessors that are installed by AUTOLOAD under no overloading. Add focused JVM and interpreter regression coverage for issue #1311. Generated with Codex (https://openai.com/codex/) Co-Authored-By: Codex --- docs/about/changelog.md | 3 ++ .../runtime/operators/MathOperators.java | 17 +++++-- .../unit/issue_1311_autoload_modulus.t | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/unit/issue_1311_autoload_modulus.t diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 0eebcb0cf4..d0fd787c27 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -9,6 +9,9 @@ priorities and future plans. - Restore Perl smartmatch dispatch for arrays, hashes, regexes, predicates, tied hashes, overloaded objects, and both execution backends. +- Preserve Perl's divisor-sign modulus semantics in dynamically compiled + methods under `no overloading`. + - Restore file-test error, stat-cache, glob-reference, and `tell` bareword behavior while preserving `${^LAST_FH}` for ordinary scalar arguments. diff --git a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java index d5a8854784..2d9d477ff1 100644 --- a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java @@ -1256,6 +1256,15 @@ public static RuntimeScalar integerModulus(RuntimeScalar arg1, RuntimeScalar arg return new RuntimeScalar(result); } + /** Integer modulus with Perl's divisor-sign result rule. */ + private static RuntimeScalar modulusFromLongs(long dividend, long divisor) { + long result = dividend % divisor; + if (result != 0 && ((divisor > 0 && result < 0) || (divisor < 0 && result > 0))) { + result += divisor; + } + return getScalarInt(result); + } + /** Modulus when at least one operand is already a DOUBLE (see {@link #modulus}). */ private static RuntimeScalar modulusFromDoubles(double dividend, double divisor) { if (divisor == 0.0) { @@ -1686,8 +1695,10 @@ private static RuntimeScalar arith(RuntimeScalar a, RuntimeScalar b, int op) { case 3 -> y != 0 && x % y == 0 ? getScalarInt(x / y) : new RuntimeScalar((double) x / (double) y); - case 4 -> y != 0 ? getScalarInt(x % y) - : new RuntimeScalar((double) x % (double) y); + // Perl's modulus result has the divisor's sign. Do not use + // Java's raw remainder here: this no-overload path is used by + // dynamically compiled methods under `no overloading` too. + case 4 -> modulusFromLongs(x, y); case 5 -> new RuntimeScalar(Math.pow(x, y)); default -> throw new IllegalStateException(); }; @@ -1697,7 +1708,7 @@ private static RuntimeScalar arith(RuntimeScalar a, RuntimeScalar b, int op) { case 1 -> integerResult(BigInteger.valueOf(x).subtract(BigInteger.valueOf(y))); case 2 -> integerResult(BigInteger.valueOf(x).multiply(BigInteger.valueOf(y))); case 3 -> new RuntimeScalar((double) x / (double) y); - case 4 -> new RuntimeScalar((double) x % (double) y); + case 4 -> modulusFromDoubles((double) x, (double) y); case 5 -> new RuntimeScalar(Math.pow((double) x, (double) y)); default -> throw new IllegalStateException(); }; diff --git a/src/test/resources/unit/issue_1311_autoload_modulus.t b/src/test/resources/unit/issue_1311_autoload_modulus.t new file mode 100644 index 0000000000..0bd237d796 --- /dev/null +++ b/src/test/resources/unit/issue_1311_autoload_modulus.t @@ -0,0 +1,45 @@ +use strict; +use warnings; +use Test::More tests => 2; + +{ + package Issue1311AutoLoader; + + sub install_autoloaded_method { + my $code = q{ + sub local_day_of_week { + my $self = shift; + return 1 + (($self->day_of_week - $self->{locale}->first_day_of_week) % 7); + } + }; + no overloading; + eval $code; + die $@ if $@; + } + + sub new { + my ($class, $day_of_week, $first_day_of_week) = @_; + return bless { + day_of_week => $day_of_week, + locale => bless({ first_day_of_week => $first_day_of_week }, 'Issue1311AutoLoader::Locale'), + }, $class; + } + + sub day_of_week { + return $_[0]->{day_of_week}; + } + + package Issue1311AutoLoader::Locale; + + sub first_day_of_week { + return $_[0]->{first_day_of_week}; + } +} + +my $date = Issue1311AutoLoader->new(2, 7); +Issue1311AutoLoader::install_autoloaded_method(); + +is($date->day_of_week - $date->{locale}->first_day_of_week, -5, + 'fixture has a negative weekday offset'); +is($date->local_day_of_week, 3, + 'method compiled by AUTOLOAD under no overloading uses Perl modulus semantics');