"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.
This means we can get rid of the special case in TransformDisplayClassUsage, as compound.assign can now also be used with the address of a local variable.