An instance compound assignment operator has no callable spelling: its call
sites can only be written as "x op= y" or "x++", and C# requires x to be an
assignable variable whose static type binds the operator the call names. The
reader therefore parks an object-typed receiver in a stack slot typed with the
operator's declaring type, and the passes that would substitute or retype such
a receiver hold back. CallInstruction vetoes unfit receiver replacements
through SatisfiesSlotRestrictionForInlining - not writable, not a storage
location, or of a type that declares a hiding operator of the same name or its
checked/unchecked sibling (a declaration-existence check on the type system,
deliberately not a binding question) - which covers inlining; copy propagation
asks the same check per receiver load, the using-transform hands receiver
loads a writable copy of the read-only using variable, and stack slots that
were typed on purpose keep their type when translated.
Assisted-by: Claude:claude-opus-5:Claude Code
The C# debug-steps view highlights and centers the exact AST node a
transform changed; the ILAst view already had the step tree and
replay-at-step but produced no highlight. Bring it to parity.
IL rendering has no token-writer seam like the C# output visitor, so
per-instruction text spans are recorded by bracketing
ILInstruction.WriteTo via a new INodeTrackingOutput. The dominant
inst.ReplaceWith(newInst) transform pattern detaches the instruction
passed to Step, so ILTransformContext gains EndStep to record the
produced instruction; Stepper additionally records the position's
ancestor chain as fallback candidates before the step-limit throw, so
the "show state before" view -- which halts at the selected step --
still resolves to a surviving ancestor (ultimately the ILFunction).
The highlight-range resolver is shared with the C# language.
Assisted-by: Claude:claude-opus-4-8:Claude Code
This was left-over from earlier versions; but ILSpy stopped caring so much about variable vs. stack slot since Roslyn started to optimize more aggressively.
The change of variable type caused problems for debug information and could even cause an assertion.
Closes#1456, closes#1562.
The C# compiler only expects:
- EnumeratorType GetEnumerator(); on the type of the in-expression.
- EnumeratorType must implement the following members:
* bool MoveNext();
* T Current { get; }