Skip to content

Fix GS9998 for member index-assignment in state machines (#887)#888

Merged
DavidObando merged 1 commit into
mainfrom
DavidObando/issue-887-clr-indexer-assign-statemachine
Jun 19, 2026
Merged

Fix GS9998 for member index-assignment in state machines (#887)#888
DavidObando merged 1 commit into
mainfrom
DavidObando/issue-887-clr-indexer-assign-statemachine

Conversation

@DavidObando

Copy link
Copy Markdown
Owner

Fixes #887.

Problem

A member CLR-indexer write such as psi.Environment["k"] = v or obj.Map["k"] = v (on a Dictionary) inside an async func failed at emit:

error GS9998: InvalidOperationException: Variable '<idxAsn#>' has no local slot or parameter index in the current method.

The LSP showed no errors while editing; the failure only surfaced at emit time.

Root cause

The index write binds to a synthesized receiver temp (<idxAsn#>) carried on a BoundClrIndexAssignmentExpression. That node stores its receiver as a raw VariableSymbol Target which the bound-tree rewriter never visits. The async capture walker correctly hoists the temp into a state-machine field, but the MoveNext rewriter only redirects BoundVariableExpression references — so the raw symbol survived unrewritten and the emitter had no slot for it.

The async MoveNext rewriter already handled the native map/slice variant (BoundIndexAssignmentExpression) but was missing the CLR-indexer override. While investigating I found the iterator and async-iterator MoveNext builders had the same latent gap for both index-assignment node kinds.

Fix

Add the missing MoveNext-rewriter overrides so a hoisted index-assignment Target is redirected to its state-machine field via the existing WithExpressionTarget form (the same approach as the closure-boxing fix, issue #618):

  • src/Core/CodeAnalysis/Lowering/Async/MoveNextBodyRewriter.csRewriteClrIndexAssignmentExpression
  • src/Core/CodeAnalysis/Lowering/Iterators/IteratorMoveNextBodyBuilder.cs — both index-assignment overrides (in both nested rewriters)
  • src/Core/CodeAnalysis/Lowering/Iterators/AsyncIteratorMoveNextBodyBuilder.cs — both index-assignment overrides

Tests

New Issue887ClrIndexerAssignmentAsyncEmitTests.cs covers async (including the exact ProcessStartInfo object-initializer + Environment[...] repro from the issue), iterator, and async-iterator. All pass with correct runtime results.

  • Full Core.Tests: 3340 passed, 1 skipped.
  • Compiler.Tests async/iterator subset: 116 passed.

A member CLR-indexer write such as `psi.Environment["k"] = v` or
`obj.Map["k"] = v` inside an `async func` failed at emit with
`GS9998: Variable '<idxAsn#>' has no local slot or parameter index`.

The index write binds to a synthesized receiver temp (`<idxAsn#>`) carried
on a BoundClrIndexAssignmentExpression. That node stores its receiver as a
raw VariableSymbol Target which the bound-tree rewriter never visits. The
async capture walker hoists the temp into a state-machine field, but the
MoveNext rewriter only redirects BoundVariableExpression references, so the
raw symbol survived unrewritten and the emitter had no slot for it.

The async MoveNext rewriter already handled the native map/slice variant
(BoundIndexAssignmentExpression) but was missing the CLR-indexer override.
The iterator and async-iterator MoveNext builders were missing overrides for
both index-assignment node kinds.

Add the missing overrides so a hoisted index-assignment Target is redirected
to its state-machine field via the WithExpressionTarget form (same approach
as the closure-boxing fix, issue #618), across all three state-machine
rewriters. Add regression tests covering async, iterator, and async-iterator.
@DavidObando DavidObando merged commit 23428d0 into main Jun 19, 2026
7 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.

Object initializer block in async function produces GS9998

1 participant