Skip to content

iterinliner: rename captured locals inside closures in a copied for body - #2575

Merged
Araq merged 1 commit into
nim-lang:masterfrom
tokyovigilante:fix-for-body-closure-capture
Sep 23, 2026
Merged

Araq merged 1 commit into
nim-lang:masterfrom
tokyovigilante:fix-for-body-closure-capture

Conversation

@tokyovigilante

Copy link
Copy Markdown
Contributor

When an inline iterator is expanded, each yield gets a copy of the for body with fresh names for the locals and labels it declares (collectDefs + copyFreshened). copyFreshened copied nested routines verbatim, so a closure declared in the body kept referring to the body's locals by their OLD names; the declarations were renamed away and a later pass hit [Bug] could not find symbol: v.0:

proc populate(n: int): int =
  result = 0
  for d in 0 ..< n:
    var v = d
    proc fill(): int {.closure.} = v
    result += fill()

Nested routines are now copied through the same renaming, which touches only the enclosing body's names (the mapping holds nothing the routine declares itself). Each copy also gets its own name for the routine, so an iterator with several yields does not define one closure symbol per copy under a single name. The same path serves expandInlineIterator, so a closure inside an inline iterator's body capturing the iterator's own locals is fixed too. Templates, macros, types and pragmas are still copied verbatim.

… body

When an inline iterator is expanded, each `yield` gets a copy of the `for`
body with fresh names for the locals and labels it declares
(`collectDefs` + `copyFreshened`). `copyFreshened` copied nested routines
verbatim, so a closure declared in the body kept referring to the body's
locals by their OLD names; the declarations were renamed away and a later
pass hit `[Bug] could not find symbol: v.0`:

    proc populate(n: int): int =
      result = 0
      for d in 0 ..< n:
        var v = d
        proc fill(): int {.closure.} = v
        result += fill()

Nested routines are now copied through the same renaming, which touches
only the enclosing body's names (the mapping holds nothing the routine
declares itself). Each copy also gets its own name for the routine, so an
iterator with several yields does not define one closure symbol per copy
under a single name. The same path serves `expandInlineIterator`, so a
closure inside an inline iterator's body capturing the iterator's own
locals is fixed too. Templates, macros, types and pragmas are still copied
verbatim.

Found porting sumi (a closure over pNext-chained locals inside a
`for d in devices:` loop). `hastur all`: 903/903.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Araq
Araq merged commit b2c2df7 into nim-lang:master Sep 23, 2026
9 checks passed
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.

2 participants