Skip to content

[mypyc] Fix range loop variable off-by-one after loop exit#21098

Open
VaggelisD wants to merge 1 commit intopython:masterfrom
VaggelisD:fix-range-loop-overshoot
Open

[mypyc] Fix range loop variable off-by-one after loop exit#21098
VaggelisD wants to merge 1 commit intopython:masterfrom
VaggelisD:fix-range-loop-overshoot

Conversation

@VaggelisD
Copy link
Contributor

Fixes mypyc/mypyc#1191

Previously, ForRange.gen_step() updated both the internal index register and the user-visible loop variable after incrementing. This meant the loop variable was set to the incremented value before the condition check could reject it, causing an off-by-one overshoot on loop exit.

  • Before:
  ┌─────────────────────────────────────┐                                                                                                                                                      
  │ L1: condition                       │                                                                                                                                                      
  │   if index_reg < end → L2 else L4   │                                                                                                                                                       
  └──────────────┬──────────────────┬───┘                                                                                                                                                      
                 │ true             │ false                                                                                                                                                    
                 ▼                  │                                                                                                                                                          
  ┌─────────────────────────────────┐   │                                                                                                                                                      
  │ L2: body                        │   │                         
  │   ... use index_target ...      │   │                                                                                                                                                      
  └──────────────┬──────────────────┘   │                                                                                                                                                      
                 ▼                      │
  ┌─────────────────────────────────┐   │                                                                                                                                                      
  │ L3: step                        │   │                         
  │   index_reg = index_reg + step  │   │                                                                                                                                                      
  │   index_target = index_reg  ◄── BUG │
  │   goto L1                       │   │                                                                                                                                                      
  └─────────────────────────────────┘   │                         
                                        ▼                                                                                                                                                      
                                ┌───────────────┐                 
                                │ L4: exit      │                                                                                                                                              
                                │ index_target  │
                                │ = overshot!   │                                                                                                                                              
                                └───────────────┘                                                                                                                                              

  • After:

  ┌─────────────────────────────────────┐
  │ L1: condition                       │
  │   if index_reg < end → L2 else L4   │
  └──────────────┬──────────────────┬───┘                                                                                                                                                      
                 │ true             │ false
                 ▼                  │                                                                                                                                                          
  ┌─────────────────────────────────┐   │                         
  │ L2: body                        │   │                                                                                                                                                      
  │   index_target = index_reg  ◄── FIX │
  │   ... use index_target ...      │   │                                                                                                                                                      
  └──────────────┬──────────────────┘   │                         
                 ▼                      │                                                                                                                                                      
  ┌─────────────────────────────────┐   │
  │ L3: step                        │   │                                                                                                                                                      
  │   index_reg = index_reg + step  │   │                         
  │   goto L1                       │   │
  └─────────────────────────────────┘   │                                                                                                                                                      
                                        ▼
                                ┌───────────────┐                                                                                                                                              
                                │ L4: exit      │                 
                                │ index_target  │
                                │ = correct ✓   │
                                └───────────────┘ 

@VaggelisD VaggelisD force-pushed the fix-range-loop-overshoot branch from 04456ad to a123591 Compare March 24, 2026 12:25
Previously, ForRange.gen_step() updated both the internal index register
and the user-visible loop variable after incrementing. This meant the
loop variable was set to the incremented value before the condition check
could reject it, causing an off-by-one overshoot on loop exit.

Move the user-visible variable assignment to begin_body(), which runs
after the condition check passes. The internal index register is still
incremented in gen_step() but no longer propagated to the user variable
until the next iteration's condition succeeds.

Fixes mypyc/mypyc#1191
@VaggelisD VaggelisD force-pushed the fix-range-loop-overshoot branch from a123591 to 0cadfe6 Compare March 24, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Range loop variable off-by-one step

1 participant