Every converter now states the Expression.* call it matches and the ILAst it
produces, and the argument-count switches label the factory overload each case
stands for. The shapes were read off ILAst dumps of compiled expression trees
rather than from the factory signatures; two branches are documented as
unreachable, since no arithmetic or logical factory declares the four-argument
(left, right, liftToNull, method) overload their case matches.
Also drops the result-type local left in ConvertField, which BuildField
re-derives from the field and the type hint.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
ConvertConstant is its only caller, and with the result type gone the
out parameter that reconstructed it - a switch over LdNull/LdStr/Ldc* -
has no consumer. What remains is a match condition: the two-argument
Expression.Constant overload must pass its type as typeof(T), while the
one-argument overload legacy csc emits for display-class instances has
nothing to check.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
Every converter handed back a (Func<ILInstruction>, IType) pair, and the
IType was consumed by matchers that ran before the builder. Now that the
ILAst instructions carry their own types, InferType() on the built operand
answers the same questions, so the type-dependent decisions move into the
builders and the pair collapses to the builder alone. Builders that could
already fail (ConvertArrayIndex) set the precedent for returning null from
inside one; ConvertInstruction propagates that.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
ConvertBind is the only expression-tree converter whose IType nobody reads:
ConvertMemberInit, its sole caller, takes Item1 and discards the rest, and
the member type is recoverable from the Call/StObj it builds anyway.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
System.Linq.Expressions resolves the user-defined operator behind a binary
factory by metadata name (Expression.Add looks up op_Addition), and the
checked factories reuse the unchecked names: AddChecked also looks up
op_Addition, never op_CheckedAddition. Recording that name at the call site
keeps the mapping next to the factory it belongs to.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
Change InferType() to an abstract method and implement for every ILInstruction.
With this change, we now always have enough information to create a variable of an appropriate type to store the result of evaluating the instruction.
This previously was not the case for instructions producing "other value type", for which the stacktype-based fallback incorrectly produced `object`.
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
- Never introduce casts for ldobj-address-chains
- Add special-case to TransformExpressionTrees: transform addressof(ldloc) to ldloca
- Classify foreach and using variables as readonly lvalues