There's an additional local variable when decompiling the non-optimized code; and explicitly putting that variable
into the test case just makes it fail due to yet another additional variable.
'this' and 'base' both read the 'this' parameter of the function being
decompiled, but their resolve result did not say so: consumers that key on
ILVariableResolveResult (local-reference output, highlighting, hover) could
not connect the keyword to the variable, and the qualified/unqualified
spellings of the same access carried differently shaped annotations.
The resolver has no ILFunction and thus no variable to put into a
ThisResolveResult, so it stops synthesizing one: LookInCurrentType looks
the name up against the (self-parameterized) current type, which grants
the same protected access, and the annotation of an unqualified field
access is built from the translated target instead. ResolveThisReference
and ResolveBaseReference had no callers left and are removed.
Assisted-by: Claude:claude-fable-5:Claude Code
Deconstruction into a pointer target ((*p, value) = tuple;) stayed an
explicit Deconstruct call: a store through a pointer (or through a target
whose pointer type got erased in a stack slot) does not infer a
ByReferenceType, so IsAssignment reported an unknown expected type and the
transform's conversion check rejected the assignment. The type of the store
itself is just as precise, so use it as the expected type.
Of the three IsAssignment call sites only the transform's MatchAssignment
consumes the expected type; CheckInvariant and GetAssignmentIndex discard
it, so this widens what the transform accepts without weakening the
invariant check.
Assisted-by: Claude:claude-opus-5:Claude Code
The events introduced in #2519 timed only the five per-entity DoDecompile
overloads, allocated a Stopwatch and the member's FullName even when no
trace session was attached, and reported whole milliseconds, which rounds
almost every member to zero. Flat one-shot events also gave PerfView and
dotnet-trace no way to show durations or nesting, and the actually
expensive stages (type system initialization, assembly resolution probing,
the IL/AST transform pipelines, whole-project decompilation) were not
instrumented at all.
Start/Stop event pairs let trace viewers derive duration and nesting from
event timestamps, keywords let a session enable only the areas of
interest, and every call site is gated on IsEnabled() so tracing costs a
branch when disabled. Per-transform events are Verbose because of their
volume; unlike the STEP/Stepper mechanism they work in Release builds.
EventSource is in-box for netstandard2.0 and flows over both ETW and
EventPipe, so this stays cross-platform with no new dependency.
Assisted-by: Claude:claude-fable-5:Claude Code
Backfills the standard MIT X11 header on hand-written files that never
got one, attributing each to its first-commit author and year from git
history. Code vendored from dotnet/runtime and Humanizr/Humanizer gets
its origin's license lines and a provenance note instead. Generated
files (Resources.Designer.cs, the version-info template), tool-managed
suppression files, and BAML test-case fixtures intentionally stay
header-less.
Assisted-by: Claude:claude-fable-5:Claude Code
The debug-step node bracket (MarkNodeStart/try/finally/MarkNodeEnd) was copied
into every hand-written WriteTo override and re-emitted by the T4 generator, so
a newly added instruction could silently omit it and lose step highlighting with
no compile error or test failure. Seal WriteTo on ILInstruction to apply the
bracket once and delegate to a new abstract WriteToCore; move every override
(hand-written and generated) to WriteToCore without the wrapper. Rendered output
is unchanged -- the marks are no-ops unless the output tracks nodes.
Assisted-by: Claude:claude-opus-4-8: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
TokenRole was a printer-side descriptor whose only jobs were holding a token's
text and giving the writers an identity to single out specific tokens. The text
becomes plain const strings on the nodes, and the few identity checks are
reexpressed as node-stack context: interpolation braces are recognized by an
Interpolation on the writer's stack, record class versus struct coloring keys
off TypeDeclaration.ClassType, and the accessor/this/base/override cases fall
out of the surrounding node. WriteKeyword/WriteToken drop the descriptor
parameter. The constants are named for what the token is: a keyword, a symbol
token, or a modifier.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The C# 9 IntPtr / UIntPtr guard in IsBinaryCompatibleWithType read
type.Kind is not TypeKind.NInt or TypeKind.NUInt
which parses as `(is not NInt) or (is NUInt)` — true unless
Kind == NInt. The intent (per the surrounding comment "but not nint
or C# 11 IntPtr") is "Kind is neither NInt nor NUInt", which needs
parentheses around the alternation:
type.Kind is not (TypeKind.NInt or TypeKind.NUInt)
Effect: when Kind == NUInt the branch no longer mistakenly applies
the C# 9 IntPtr-only restrictions (suppressing compound assignment
without nint, disallowing shifts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This way we avoid having to extract later, as we will never inline if the `isinst` argument if this could result in it being unrepresentable in C#.
This commit also refactors inlining restrictions to avoid requiring special cases in ILInlining itself.
But when making this change, I discovered that this broke our pattern-matching tests, and that the weird IL with double `isinst` is indeed generated by the C# compiler for `if (genericParam is StringComparison.Ordinal)` style code. So instead we also allow `isinst` with a `box(expr-without-side-effects)` argument to be represented with the `expr is T ? (T)expr : null` emulation.