Skip to content

A scalar op-assign on a global or class variable ($i += v) is the raw C operator: no overflow check, **= does not compile, and the rhs runs before the slot is read #4886

Description

@elektronaut

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

nil

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions