"x op= y" and a prefix increment resolve in two phases: the instance operators
reachable from the static type of x come first wherever x is a variable -
whether or not the result is used - and the static operators only if none of
them is applicable. A postfix increment whose result is used is the one form
that always binds a static operator. CallBuilder models the first phase when it
checks that recompiling an instance operator call binds the same method; the
fallback phase is deliberately not modeled, since a call the instance
candidates cannot account for must give up the operator form rather than
collide with a static operator.
The same rule cuts the other way for the folds built from static operator
calls: a shadowed fold has to be written in one of the static-binding forms.
The binary operators become "x = x + y"; an increment becomes a postfix
increment whose result goes to a discard, "_ = x++;". That form only exists as
a statement, so the folds that would embed a shadowed increment in an
expression hold back and FixRemainingIncrements gives the increment a statement
of its own instead. A foreach variable cannot be such a receiver at all (it is
read-only), so the loop keeps the existing variable as a writable copy, both
for a plain loop variable and for a deconstruction.
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
For user-defined increments, there were problems with Roslyn was optimizing out one of the stores.
The new transform FixRemainingIncrements now takes increments/decrements that were not detected by TransformAssignment and introduces a temporary variable that can be incremented.
This sometimes requires un-inlining via the new ILInstruction.Extract() operation.
Extract() is not supported in all possible contexts, so it is possible but unlikely that some op_Increment calls remain.
For decimals, the situation is different: legacy csc actually was optimizing "d + 1m" to "op_Increment(d)", so we can get rid of any left-over increments by undoing this optimization. This now happens in ReplaceMethodCallsWithOperators.