diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ceaa4b6561..ab324980c5 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,6 +46,8 @@ jobs: run: make ci env: GRADLE_OPTS: "-Dorg.gradle.daemon=false" + # Windows timeout.exe only sleeps; Git for Windows supplies GNU timeout.exe. + PERLONJAVA_TIMEOUT_COMMAND: 'C:\Program Files\Git\usr\bin\timeout.exe' - name: Run focused Perl thread gate (Windows) id: threads-windows diff --git a/docs/about/changelog.md b/docs/about/changelog.md index c915f5c7bb..6f7b18e3e5 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -6,6 +6,9 @@ priorities and future plans. ## Work in progress +- Restore Perl-compatible integer increment/decrement semantics, imprecision + warnings, numeric overload fallback, and postfix-reference lifetime handling. + - Preserve Perl control-verb boundaries through nested common-prefix regex alternatives, restoring `re/regexp.t` compatibility on both backends. diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index d02dc40118..0e90286607 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -2420,6 +2420,18 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) { pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); } + case Opcodes.INTEGER_PRE_AUTOINCREMENT -> + pc = OpcodeHandlerExtended.executeIntegerPreAutoIncrement(bytecode, pc, registers); + + case Opcodes.INTEGER_POST_AUTOINCREMENT -> + pc = OpcodeHandlerExtended.executeIntegerPostAutoIncrement(bytecode, pc, registers); + + case Opcodes.INTEGER_PRE_AUTODECREMENT -> + pc = OpcodeHandlerExtended.executeIntegerPreAutoDecrement(bytecode, pc, registers); + + case Opcodes.INTEGER_POST_AUTODECREMENT -> + pc = OpcodeHandlerExtended.executeIntegerPostAutoDecrement(bytecode, pc, registers); + // ================================================================= // ERROR HANDLING // ================================================================= diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 5e305826d9..08d571a2fd 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -836,6 +836,13 @@ private static void visitFileTestOp(BytecodeCompiler bc, OperatorNode node, Stri private static void visitIncrDecr(BytecodeCompiler bc, OperatorNode node, String op) { boolean isPostfix = op.endsWith("postfix"); boolean isIncrement = op.startsWith("++"); + short autoOpcode = bc.isIntegerEnabled() + ? (isIncrement + ? (isPostfix ? Opcodes.INTEGER_POST_AUTOINCREMENT : Opcodes.INTEGER_PRE_AUTOINCREMENT) + : (isPostfix ? Opcodes.INTEGER_POST_AUTODECREMENT : Opcodes.INTEGER_PRE_AUTODECREMENT)) + : (isIncrement + ? (isPostfix ? Opcodes.POST_AUTOINCREMENT : Opcodes.PRE_AUTOINCREMENT) + : (isPostfix ? Opcodes.POST_AUTODECREMENT : Opcodes.PRE_AUTODECREMENT)); Node operand = node.operand; while (operand instanceof ListNode list && list.elements.size() == 1) { operand = list.elements.getFirst(); @@ -850,12 +857,12 @@ private static void visitIncrDecr(BytecodeCompiler bc, OperatorNode node, String int varReg = bc.getVariableRegister(varName); if (isPostfix) { int resultReg = bc.allocateRegister(); - bc.emit(isIncrement ? Opcodes.POST_AUTOINCREMENT : Opcodes.POST_AUTODECREMENT); + bc.emit(autoOpcode); bc.emitReg(resultReg); bc.emitReg(varReg); bc.lastResultReg = resultReg; } else { - bc.emit(isIncrement ? Opcodes.PRE_AUTOINCREMENT : Opcodes.PRE_AUTODECREMENT); + bc.emit(autoOpcode); bc.emitReg(varReg); bc.lastResultReg = varReg; } @@ -866,12 +873,12 @@ private static void visitIncrDecr(BytecodeCompiler bc, OperatorNode node, String int operandReg = bc.lastResultReg; if (isPostfix) { int resultReg = bc.allocateRegister(); - bc.emit(isIncrement ? Opcodes.POST_AUTOINCREMENT : Opcodes.POST_AUTODECREMENT); + bc.emit(autoOpcode); bc.emitReg(resultReg); bc.emitReg(operandReg); bc.lastResultReg = resultReg; } else { - bc.emit(isIncrement ? Opcodes.PRE_AUTOINCREMENT : Opcodes.PRE_AUTODECREMENT); + bc.emit(autoOpcode); bc.emitReg(operandReg); bc.lastResultReg = operandReg; } diff --git a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java index 1c6a487903..072567b10b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java @@ -940,6 +940,24 @@ public static String disassemble(InterpretedCode interpretedCode) { int postDecSrc = interpretedCode.bytecode[pc++]; sb.append("POST_AUTODECREMENT r").append(rd).append(" = r").append(postDecSrc).append("--\n"); break; + case Opcodes.INTEGER_PRE_AUTOINCREMENT: + rd = interpretedCode.bytecode[pc++]; + sb.append("INTEGER_PRE_AUTOINCREMENT ++r").append(rd).append("\n"); + break; + case Opcodes.INTEGER_POST_AUTOINCREMENT: + rd = interpretedCode.bytecode[pc++]; + int integerPostIncSrc = interpretedCode.bytecode[pc++]; + sb.append("INTEGER_POST_AUTOINCREMENT r").append(rd).append(" = r").append(integerPostIncSrc).append("++\n"); + break; + case Opcodes.INTEGER_PRE_AUTODECREMENT: + rd = interpretedCode.bytecode[pc++]; + sb.append("INTEGER_PRE_AUTODECREMENT --r").append(rd).append("\n"); + break; + case Opcodes.INTEGER_POST_AUTODECREMENT: + rd = interpretedCode.bytecode[pc++]; + int integerPostDecSrc = interpretedCode.bytecode[pc++]; + sb.append("INTEGER_POST_AUTODECREMENT r").append(rd).append(" = r").append(integerPostDecSrc).append("--\n"); + break; case Opcodes.PRINT: { int contentReg = interpretedCode.bytecode[pc++]; int filehandleReg = interpretedCode.bytecode[pc++]; diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index f9a901ec8a..e030ef89b6 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -895,6 +895,48 @@ public static int executePostAutoDecrement(int[] bytecode, int pc, RuntimeBase[] return pc; } + public static int executeIntegerPreAutoIncrement(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + registers[rd] = scalarIncrementOperand(registers[rd]); + if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { + registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); + } + ((RuntimeScalar) registers[rd]).integerPreAutoIncrement(); + return pc; + } + + public static int executeIntegerPostAutoIncrement(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rs] = scalarIncrementOperand(registers[rs]); + if (BytecodeInterpreter.isImmutableProxy(registers[rs])) { + registers[rs] = BytecodeInterpreter.ensureMutableScalar(registers[rs]); + } + registers[rd] = ((RuntimeScalar) registers[rs]).integerPostAutoIncrement(); + return pc; + } + + public static int executeIntegerPreAutoDecrement(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + registers[rd] = scalarIncrementOperand(registers[rd]); + if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { + registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); + } + ((RuntimeScalar) registers[rd]).integerPreAutoDecrement(); + return pc; + } + + public static int executeIntegerPostAutoDecrement(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rs] = scalarIncrementOperand(registers[rs]); + if (BytecodeInterpreter.isImmutableProxy(registers[rs])) { + registers[rs] = BytecodeInterpreter.ensureMutableScalar(registers[rs]); + } + registers[rd] = ((RuntimeScalar) registers[rs]).integerPostAutoDecrement(); + return pc; + } + /** * Execute open operation. * Format: OPEN rd ctx argsReg diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 8f156214ab..0984aa4bb0 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -2539,6 +2539,18 @@ public class Opcodes { /** Alias a package hash slot to a hash register. Format: nameStringIdx hashReg. */ public static final short ALIAS_GLOBAL_HASH = 551; + /** Native-IV pre-increment under lexical {@code use integer}. */ + public static final short INTEGER_PRE_AUTOINCREMENT = 556; + + /** Native-IV post-increment under lexical {@code use integer}. */ + public static final short INTEGER_POST_AUTOINCREMENT = 557; + + /** Native-IV pre-decrement under lexical {@code use integer}. */ + public static final short INTEGER_PRE_AUTODECREMENT = 558; + + /** Native-IV post-decrement under lexical {@code use integer}. */ + public static final short INTEGER_POST_AUTODECREMENT = 559; + /** * Resolve a statically named CODE reference at runtime. This preserves the * current CV snapshot while allowing an earlier runtime glob assignment in diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index 4d90572439..01f3575118 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -44,6 +44,8 @@ public class RuntimeScalar extends RuntimeBase implements RuntimeScalarReference */ private transient StringBuilder growingString; private transient boolean transferableGrowingString; + /** Number of imprecision diagnostics already emitted for a run of auto-operations. */ + private transient int imprecisionAutoWarningCount; /** Live substr lvalues that must be refreshed when this scalar is replaced. */ private transient List> substrLvalueObservers; @@ -495,6 +497,9 @@ void retainClosureCaptureReferentsForUnweaken() { */ public boolean refCountOwned; + /** A postfix ++/-- result transfers, rather than duplicates, its reference owner on assignment. */ + private boolean postfixReferenceOwnershipTransfer; + /** * True for the synthetic {@code $_[0]} scalar passed to DESTROY. * Perl treats that invocant as read-only for operations such as @@ -1608,7 +1613,17 @@ public void addToArray(RuntimeArray runtimeArray) { * @param scalar The RuntimeScalar object */ public RuntimeScalar addToScalar(RuntimeScalar scalar) { - return scalar.set(this); + RuntimeScalar result = scalar.set(this); + if (postfixReferenceOwnershipTransfer) { + postfixReferenceOwnershipTransfer = false; + if ((type & REFERENCE_BIT) != 0 && value instanceof RuntimeBase base + && base.refCount > 0) { + // set() acquired the destination owner; cancel the extra + // increment because this temporary's owner was transferred. + base.refCount--; + } + } + return result; } /** @@ -4282,6 +4297,7 @@ public RuntimeScalar preAutoIncrement() { } private RuntimeScalar preAutoIncrementWithoutWatcherNotification() { + warnImprecisionForAutoOperation(1); this.numericLiteralText = null; this.numericContextSeen = false; this.firstClassRegexScalar = false; @@ -4367,7 +4383,7 @@ private RuntimeScalar preAutoIncrementWithoutWatcherNotification() { default -> { // All reference types (CODE, REFERENCE, ARRAYREFERENCE, etc.) // Check if object is eligible for overloading int blessId = blessedId(this); - if (blessId < 0) { + if (blessId != 0) { // Prepare overload context and check if object is eligible for overloading OverloadContext ctx = OverloadContext.prepare(blessId); if (ctx != null) { @@ -4390,6 +4406,22 @@ private RuntimeScalar preAutoIncrementWithoutWatcherNotification() { return this; } + // A copy overload may deliberately turn the lvalue + // into a native scalar. Do its native mutation + // before considering conversion methods from the + // original object's overload table. + if (copiedToPlainScalar) { + return preAutoIncrementWithoutWatcherNotification(); + } + + // With fallback enabled, Perl autogenerates ++ from + // numeric conversion when no ++ or + overload exists. + result = ctx.tryOverloadFallback(this, "(0+"); + if (result != null) { + set(result); + return preAutoIncrementWithoutWatcherNotification(); + } + // Try fallback to + operator with undef as third argument (mutator indicator) result = ctx.tryOverload("(+", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { @@ -4399,12 +4431,6 @@ private RuntimeScalar preAutoIncrementWithoutWatcherNotification() { return this; } - // An explicit copy constructor may return a plain value. - // Perl applies the native increment to that copied value - // when neither ++ nor + supplies the mutation. - if (copiedToPlainScalar) { - return preAutoIncrementWithoutWatcherNotification(); - } } } @@ -4428,7 +4454,86 @@ public RuntimeScalar postAutoIncrement() { return result; } + /** Implements ++ while lexical {@code use integer} is active. */ + public RuntimeScalar integerPreAutoIncrement() { + watcherMutationDepth++; + RuntimeScalar result; + try { + result = integerAutoIncrement(false, 1); + } finally { + watcherMutationDepth--; + } + notifyModifiedWatchers(); + return result; + } + + /** Implements postfix ++ while lexical {@code use integer} is active. */ + public RuntimeScalar integerPostAutoIncrement() { + watcherMutationDepth++; + RuntimeScalar result; + try { + result = integerAutoIncrement(true, 1); + } finally { + watcherMutationDepth--; + } + notifyModifiedWatchers(); + return result; + } + + /** Implements -- while lexical {@code use integer} is active. */ + public RuntimeScalar integerPreAutoDecrement() { + watcherMutationDepth++; + RuntimeScalar result; + try { + result = integerAutoIncrement(false, -1); + } finally { + watcherMutationDepth--; + } + notifyModifiedWatchers(); + return result; + } + + /** Implements postfix -- while lexical {@code use integer} is active. */ + public RuntimeScalar integerPostAutoDecrement() { + watcherMutationDepth++; + RuntimeScalar result; + try { + result = integerAutoIncrement(true, -1); + } finally { + watcherMutationDepth--; + } + notifyModifiedWatchers(); + return result; + } + + private RuntimeScalar integerAutoIncrement(boolean postfix, int delta) { + // Objects and tied variables retain the normal mutation protocol: it + // performs overload dispatch and FETCH/STORE before any native-IV + // coercion could be considered. + if (blessedId(this) != 0 || this.type == RuntimeScalarType.TIED_SCALAR + || this.type == RuntimeScalarType.GLOB + || this.type == RuntimeScalarType.READONLY_SCALAR) { + return delta > 0 + ? (postfix ? postAutoIncrementWithoutWatcherNotification() + : preAutoIncrementWithoutWatcherNotification()) + : (postfix ? postAutoDecrementWithoutWatcherNotification() + : preAutoDecrementWithoutWatcherNotification()); + } + + // Perl gives postfix ++ on undef the old numeric zero, but postfix -- + // returns the original undef before coercing the lvalue to -1. + RuntimeScalar old = this.type == RuntimeScalarType.UNDEF && delta > 0 + ? new RuntimeScalar(0) : new RuntimeScalar(this); + this.numericLiteralText = null; + this.numericContextSeen = false; + this.firstClassRegexScalar = false; + this.formatPictureTainted = false; + setIntegerValue(getLong() + delta); + return postfix ? old : this; + } + private RuntimeScalar postAutoIncrementWithoutWatcherNotification() { + warnImprecisionForAutoOperation(1); if (this.type != RuntimeScalarType.TIED_SCALAR && this.type != RuntimeScalarType.STRING && this.type != RuntimeScalarType.BYTE_STRING @@ -4524,9 +4629,22 @@ private RuntimeScalar postAutoIncrementLarge() { case READONLY_SCALAR -> // 12 throw new PerlCompilerException("Modification of a read-only value attempted"); default -> { // All reference types + // A postfix operation returns the original reference while + // replacing this lvalue with a number. Move (rather than + // duplicate) this scalar's counted ownership to that return + // value so its eventual assignment/scope cleanup can run + // DESTROY. The copy constructor intentionally has no owner + // token of its own. + // The source may be a borrowed register alias even though the + // returned reference becomes the first durable owner. The + // assignment receiving this scalar will balance the temporary + // copy, so retain one cleanup token for that returned value. + old.refCountOwned = true; + old.postfixReferenceOwnershipTransfer = true; + this.refCountOwned = false; // Check if object is eligible for overloading int blessId = blessedId(this); - if (blessId < 0) { + if (blessId != 0) { // Prepare overload context and check if object is eligible for overloading OverloadContext ctx = OverloadContext.prepare(blessId); if (ctx != null) { @@ -4548,6 +4666,20 @@ private RuntimeScalar postAutoIncrementLarge() { return old; } + if (copiedToPlainScalar) { + preAutoIncrementWithoutWatcherNotification(); + return old; + } + + // Numeric conversion is the final autogeneration + // route for an overloaded postfix increment. + result = ctx.tryOverloadFallback(this, "(0+"); + if (result != null) { + set(result); + preAutoIncrementWithoutWatcherNotification(); + return old; + } + // Try fallback to + operator with undef as third argument (mutator indicator) result = ctx.tryOverload("(+", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { @@ -4557,10 +4689,6 @@ private RuntimeScalar postAutoIncrementLarge() { return old; } - if (copiedToPlainScalar) { - preAutoIncrementWithoutWatcherNotification(); - return old; - } } } @@ -4584,6 +4712,7 @@ public RuntimeScalar preAutoDecrement() { } private RuntimeScalar preAutoDecrementWithoutWatcherNotification() { + warnImprecisionForAutoOperation(-1); this.numericLiteralText = null; this.numericContextSeen = false; this.firstClassRegexScalar = false; @@ -4663,10 +4792,11 @@ private RuntimeScalar preAutoDecrementWithoutWatcherNotification() { default -> { // All reference types // Check if object is eligible for overloading int blessId = blessedId(this); - if (blessId < 0) { + if (blessId != 0) { // Prepare overload context and check if object is eligible for overloading OverloadContext ctx = OverloadContext.prepare(blessId); if (ctx != null) { + boolean copiedToPlainScalar = false; // Copy-on-write: If the object has the = overload, call it to create // a copy BEFORE any mutation. This implements Perl's COW semantics // where shared references are copied before modification. @@ -4675,6 +4805,7 @@ private RuntimeScalar preAutoDecrementWithoutWatcherNotification() { // Copy the cloned object's fields into this this.type = copyResult.type; this.value = copyResult.value; + copiedToPlainScalar = !RuntimeScalarType.isReference(this); } // Try direct overload method for -- @@ -4685,6 +4816,18 @@ private RuntimeScalar preAutoDecrementWithoutWatcherNotification() { return this; } + if (copiedToPlainScalar) { + return preAutoDecrementWithoutWatcherNotification(); + } + + // With fallback enabled, Perl autogenerates -- from + // numeric conversion when no -- or - overload exists. + result = ctx.tryOverloadFallback(this, "(0+"); + if (result != null) { + set(result); + return preAutoDecrementWithoutWatcherNotification(); + } + // Try fallback to - operator with undef as third argument (mutator indicator) result = ctx.tryOverload("(-", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { @@ -4721,6 +4864,7 @@ public RuntimeScalar postAutoDecrement() { private RuntimeScalar postAutoDecrementWithoutWatcherNotification() { RuntimeScalar old = new RuntimeScalar(this); + warnImprecisionForAutoOperation(-1); this.numericLiteralText = null; this.numericContextSeen = false; this.firstClassRegexScalar = false; @@ -4796,12 +4940,18 @@ private RuntimeScalar postAutoDecrementWithoutWatcherNotification() { case READONLY_SCALAR -> // 12 throw new PerlCompilerException("Modification of a read-only value attempted"); default -> { // All reference types + // See postAutoIncrementLarge(): the returned pre-mutation + // reference inherits this lvalue's ownership token. + old.refCountOwned = true; + old.postfixReferenceOwnershipTransfer = true; + this.refCountOwned = false; // Check if object is eligible for overloading int blessId = blessedId(this); - if (blessId < 0) { + if (blessId != 0) { // Prepare overload context and check if object is eligible for overloading OverloadContext ctx = OverloadContext.prepare(blessId); if (ctx != null) { + boolean copiedToPlainScalar = false; // Copy-on-write: If the object has the = overload, call it to create // a copy BEFORE any mutation. This implements Perl's COW semantics // where shared references are copied before modification. @@ -4810,6 +4960,7 @@ private RuntimeScalar postAutoDecrementWithoutWatcherNotification() { // Copy the cloned object's fields into this this.type = copyResult.type; this.value = copyResult.value; + copiedToPlainScalar = !RuntimeScalarType.isReference(this); } // Try direct overload method for -- @@ -4820,6 +4971,18 @@ private RuntimeScalar postAutoDecrementWithoutWatcherNotification() { return old; } + if (copiedToPlainScalar) { + preAutoDecrementWithoutWatcherNotification(); + return old; + } + + result = ctx.tryOverloadFallback(this, "(0+"); + if (result != null) { + set(result); + preAutoDecrementWithoutWatcherNotification(); + return old; + } + // Try fallback to - operator with undef as third argument (mutator indicator) result = ctx.tryOverload("(-", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { @@ -4838,6 +5001,19 @@ private RuntimeScalar postAutoDecrementWithoutWatcherNotification() { return old; } + /** Emit Perl's lexical imprecision warning when an NV cannot represent a unit step. */ + private void warnImprecisionForAutoOperation(int delta) { + if (this.type != INTEGER && this.type != DOUBLE) return; + if (imprecisionAutoWarningCount >= 2) return; + double numericValue = getDouble(); + if (!Double.isFinite(numericValue) || numericValue + delta != numericValue) return; + BigInteger exact = getSignedBigint(); + WarnDie.warnWithCategory(new RuntimeScalar("Lost precision when " + + (delta > 0 ? "incrementing " : "decrementing ") + exact), + scalarEmptyString, "imprecision"); + imprecisionAutoWarningCount++; + } + public RuntimeScalar chop() { return StringOperators.chopScalar(this); } diff --git a/src/test/resources/unit/overload_integer_postfix_regressions.t b/src/test/resources/unit/overload_integer_postfix_regressions.t new file mode 100644 index 0000000000..b70238f4ba --- /dev/null +++ b/src/test/resources/unit/overload_integer_postfix_regressions.t @@ -0,0 +1,35 @@ +use strict; +use warnings; +use Test::More; + +SKIP: { + # RT #43356 was added to Perl 5.44's overload behavior; the local + # standard-Perl oracle is 5.42 and preserves the pre-autogeneration result. + skip 'postfix overload autogeneration requires Perl 5.44', 1 if $] < 5.044; + + use overload + '0+' => sub { ${$_[0]} }, + '=' => sub { ${$_[0]} }, + fallback => 1; + + my $value = bless \(my $dummy = 1), __PACKAGE__; + is(++$value, 2, 'copy overload is followed by native increment'); +} + +{ + no warnings 'uninitialized'; + use integer; + + my ($value, $old); + $value = undef; + $old = $value--; + + ok(!defined($old), 'integer postfix decrement preserves undef old value'); + is($value, -1, 'integer postfix decrement coerces lvalue to minus one'); + + $value = undef; + $old = $value++; + is($old, 0, 'integer postfix increment preserves zero old-value coercion'); +} + +done_testing; diff --git a/src/test/resources/unit/regex_interpolated_heredoc_error.t b/src/test/resources/unit/regex_interpolated_heredoc_error.t index dee2eb50a7..381dc431dd 100644 --- a/src/test/resources/unit/regex_interpolated_heredoc_error.t +++ b/src/test/resources/unit/regex_interpolated_heredoc_error.t @@ -8,8 +8,9 @@ print {$fh} "s;\@{< // '';