Enable PEP 709 inlined comprehensions by youknowone · Pull Request #7412 · RustPython/RustPython

coderabbitai[bot]

bot reviewed Mar 12, 2026

coderabbitai[bot]

coderabbitai[bot]

coderabbitai[bot]

ShaharNaveh

Activate the existing compile_inlined_comprehension() implementation
by fixing 6 bugs that prevented it from working:

- LoadFastAndClear: push NULL (not None) when slot is empty so
  StoreFast can restore empty state after comprehension
- StoreFast: accept NULL from stack for the restore path
- sub_tables.remove(0) replaced with next_sub_table cursor to
  match the pattern used elsewhere in the compiler
- in_inlined_comp flag moved from non-inlined to inlined path
- is_inlined_comprehension_context() now checks comp_inlined flag
  and restricts inlining to function-like scopes
- comp_inlined set only when parent scope uses fastlocals

Symbol table analysis handles conflict detection:
- Nested scopes in comprehension → skip inlining
- Bound name conflicts with parent symbol → skip inlining
- Cross-comprehension reference conflicts → skip inlining
- Splice comprehension sub_tables into parent for nested scope tracking

@youknowone

Replace conservative bail-out approach with CPython-matching strategy:

Symbol table (symboltable.rs):
- Remove conflict-based bail-out checks (bound conflict, ref conflict,
  nested scopes)
- Implement inline_comprehension() matching CPython's symtable.c:
  track inlined_cells, handle __class__ in ClassBlock, merge symbols
  with proper is_free_in_any_child check
- Promote LOCAL to CELL for inlined_cells, set COMP_CELL flag
- Splice inlined comp children into parent's sub_tables
- Add can_see_class_scope check to inlining decision

Compiler (compile.rs):
- Implement TweakInlinedComprehensionScopes: temporarily swap symbol
  scopes during comprehension body compilation
- Implement RevertInlinedComprehensionScopes: restore original scopes
- Push ALL locally-bound names (not just iteration targets) for
  save/restore, excluding walrus operator targets
- Emit MakeCell/RestoreCell for CELL variables in comprehensions
- Re-enable async comprehension inlining with proper
  SetupFinally/EndAsyncFor handling
- Include COMP_CELL symbols in cellvars

VM (frame.rs, instruction.rs):
- Add RestoreCell instruction for proper cell save/restore
- MakeCell now saves old cell to stack and creates new empty cell
…ension cells

Register RESTORE_CELL (opcode 121) in _opcode_metadata.py so
test__opcode's stack_effect check passes.

In locals(), skip cell values when the same name has a set fastlocal
value. This fixes test_closure_with_inline_comprehension where a cell
variable used as an inlined comprehension iteration target should show
the comprehension's value, not the outer cell value.
- Add CO_FAST_LOCAL/CELL/FREE/HIDDEN constants and
  localspluskinds field to CodeObject for per-slot metadata
- Change DEREF instruction opargs from cell-relative indices
  (NameIdx) to localsplus absolute indices (oparg::VarNum)
- Add fixup_deref_opargs pass in ir.rs to convert cell-relative
  indices to localsplus indices after finalization
- Replace get_cell_name with get_localsplus_name in
  InstrDisplayContext trait
- Update VM cell_ref/get_cell_contents/set_cell_contents to use
  localsplus indices directly (no nlocals offset)
- Update function.rs cell2arg, super.rs __class__ lookup with
  explicit nlocals offsets