Environment
spinel: da7617b5 (master, 2026-09-24)
CRuby: ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin23]
cc: Apple clang version 21.0.0 (clang-2100.3.34.2)
Problem
A scalar op-assign on a global or a class variable ($i += v, -=, *=, **=, <<= and so on) is emitted as the raw C operator ref OP= v. That has four effects:
- Overflow isn't checked. In the default
--int-overflow=raise mode (docs/int-overflow.md), a local raises RangeError on overflow. A global prints nil or a wrapped value instead.
**= doesn't compile, and neither does an op-assign on a Bignum global.
- The slot is read after the right-hand side runs, so a right-hand side that reassigns it changes the result.
- Ivars share part of this: bitwise ops and Float on an ivar keep the raw operator, and the slot read is a sibling C argument of the right-hand side, so C leaves their order unspecified.
The GlobalVariableOperatorWriteNode and ClassVariableOperatorWriteNode arms in emit_stmt (src/codegen_stmt.c), and the matching value-position arms in src/codegen_expr.c, handle String + and Array slots only; every scalar falls through to the raw operator. The local's op-assign already has checked Integer, Bignum and Float arms. #4875 fixed the same ordering for Array slots.
Reproducer
$i = 2**62
$i += 2**62
p $i
Expected: RangeError, the way i = 2**62; i += 2**62 raises on the same build (spinel's default mode). CRuby promotes, to 9223372036854775808.
Actual (spinel): builds, prints
The order:
def bump
$j = 100
3
end
$j = 10
$j += bump
p $j # CRuby 13; spinel 103
Scope
238 generated programs: $i / @@i / @i / local × += -= *= /= %= **= <<= |= and Float += × statement and value × with and without overflow × a side-effecting right-hand side inline and through a method.
| Rows |
spinel |
| global, no overflow (54) |
28 wrong, silently; 6 C errors |
| class variable, no overflow (54) |
28 wrong, silently; 6 C errors |
| ivar, no overflow (54) |
12 wrong: |=, <<= and Float with a side-effecting right-hand side |
| global / class variable, overflow (20) |
16 wrong (nil, 0, wrapped), 4 C errors (**=) |
| ivar, overflow (10) |
2 wrong (<<= 64 prints an empty line) |
| local, overflow (10) |
RangeError (correct in this mode) |
This isn't a regression. Checked on da7617b.
Suggested regression test
test/scalar_op_assign_reads_slot_first.rb for the order rows, plus global and class-variable RangeError rows in test/int_overflow_op_assign.rb.
Environment
Problem
A scalar op-assign on a global or a class variable (
$i += v,-=,*=,**=,<<=and so on) is emitted as the raw C operatorref OP= v. That has four effects:--int-overflow=raisemode (docs/int-overflow.md), a local raises RangeError on overflow. A global printsnilor a wrapped value instead.**=doesn't compile, and neither does an op-assign on a Bignum global.The
GlobalVariableOperatorWriteNodeandClassVariableOperatorWriteNodearms inemit_stmt(src/codegen_stmt.c), and the matching value-position arms insrc/codegen_expr.c, handle String+and Array slots only; every scalar falls through to the raw operator. The local's op-assign already has checked Integer, Bignum and Float arms. #4875 fixed the same ordering for Array slots.Reproducer
Expected: RangeError, the way
i = 2**62; i += 2**62raises on the same build (spinel's default mode). CRuby promotes, to9223372036854775808.Actual (spinel): builds, prints
The order:
Scope
238 generated programs:
$i/@@i/@i/ local ×+= -= *= /= %= **= <<= |=and Float+=× statement and value × with and without overflow × a side-effecting right-hand side inline and through a method.|=,<<=and Float with a side-effecting right-hand side**=)<<= 64prints an empty line)This isn't a regression. Checked on da7617b.
Suggested regression test
test/scalar_op_assign_reads_slot_first.rbfor the order rows, plus global and class-variable RangeError rows intest/int_overflow_op_assign.rb.