The dotnet-hosted Roslyn 2.10 build cannot start its VBCSCompiler server
under a current dotnet host, so with /shared every test compilation first
waited out the client's full 20-second new-server connection timeout
before falling back to a sub-second in-process compile. Since the 2.10
configurations were enabled on non-Windows (#3914), that added ~29
minutes to the Linux CI job and ~43 minutes on macOS: ~340 affected
tests at ~21s each, versus ~0.2s for the toolsets whose server works.
Assisted-by: Claude:claude-fable-5:Claude Code
Review follow-up. A display-class field initialized from a non-this
parameter is now the only shape where propagation and a later mutation
coexist; it stays sound only because ResolveVariableToPropagate accepts
a parameter with LoadCount == 1, so the mutation can be redirected to
it. Nothing covered that, so Test12 pins it, and Test13 records the
neighbouring shape where the mutation happens inside a lambda - there
capturing the display class keeps it materialized and propagation never
arises. The guard predicate is renamed to say what it matches, since
'ReadOnly' reads like the C# keyword rather than 'a plain read'.
Assisted-by: Claude:claude-fable-5:Claude Code
A field that is propagated to the variable it was initialized from is
replaced by that variable, so re-emitting its initializer assigns the
variable to itself. Where the field was initialized from 'this' the
result does not even compile ('this = this'). The store is dropped
instead, which is what VisitStObj already does for initializer stores
that are not part of an object-initializer block; the insertion position
has to be tracked separately from the loop index, because skipping a
store would otherwise push the following ones past the end of the block.
Assisted-by: Claude:claude-fable-5:Claude Code
Aggressive scalar replacement propagated a display-class field to its
source variable even when the field is mutated after initialization,
aliasing two distinct source-level variables (Test9: thisField and
this). Propagation is now cancelled when the field sees a second store
or its address escapes, but only for propagation targets that cannot
absorb the store: 'this' and variables that are themselves
scalar-replaced display classes. Parameters continue to propagate,
because their remaining uses are already restricted by
ResolveVariableToPropagate and a captured parameter mutated inside a
lambda (DelegateConstruction's Bug951) must keep mapping to the
parameter. Checking CanPropagate first also keeps the guard away from
Mono state-machine fields, whose VariableToDeclare is pre-bound to a
state-machine variable that Propagate(null) would discard.
Re-enables Test9 and adds Test10 covering the escaping-address variant
(Interlocked.Exchange(ref displayClass.thisField, ...)).
Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
Assisted-by: Claude:claude-fable-5:Claude Code
The .NET 10 BCL ships static [Extension] classes that contain ordinary
nested types (e.g. XDocumentExtensions.XDocumentNavigable). Decompiling
such a nested type's member in isolation resolved the enclosing
container's ExtensionInfo, and DecompileBody then dereferenced the
missing extension-member mapping. A container without any extension
blocks now reports no ExtensionInfo at all, and ResolveExtensionInfo
applies a container's info only to members that actually belong to one
of its extension blocks.
Assisted-by: Claude:claude-fable-5:Claude Code
Avalonia windows default to ShowInTaskbar=true, so every owned modal
dialog (Open from GAC/NuGet feed/running process, Manage Assembly
Lists, Export Project, Create List, Set Target Framework) got its own
taskbar button, which is not standard Windows behavior. The assertion
and crash dialogs intentionally keep their buttons so an interrupted
session stays easy to find.
Assisted-by: Claude:claude-fable-5:Claude Code
ProDataGrid derives its scroll extent from the current scroll offset, so an
offset that is briefly too large inflates the extent, which permits a larger
offset again. A trackpad reaches that state within a few hundred sub-row
events: instrumented in the running app, a ~1500px list reported 8735px and
kept growing, the thumb collapsed to its minimum, and the end of the list ran
away from the user. A second defect slid the rows sideways by up to 10px - the
star-sized column is measured against the width including the vertical scroll
bar, leaving the grid convinced it has a scroll bar's worth of content to
reach.
Disabling the horizontal scroll bar, giving each grid its own
DefaultRowHeightEstimator, pinning RowHeight, forcing the vertical scroll bar
visible, and disabling scrolling on the template's inner ScrollViewer each
removed a symptom at most; none can break a loop that runs through the scroll
offset, and 12.0.4 is the newest release. A ListBox's virtualizing panel keeps
the extent a function of the items alone. The price is laying out the columns
here - so the header row and the item template have to be kept in step - and
losing sortable, resizable headers.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
A collection that fails once the session has been granted - most plausibly
the target exiting before the stop command reaches it - left the task copying
the trace connection behind. That connection is torn down on the way out, the
copy faults, nobody awaits it, and the finalizer hands it to
TaskScheduler.UnobservedTaskException, which this app reports as a crash: a
second report of a failure the dialog's error bar had already explained
correctly, minutes later and detached from the gesture that caused it. The
CollectTracing2 fallback walks the same path, so one dying target produced two
of them.
The teardown order is the substance of the fix. The session connection has to
go first, because after the failure nothing else will ever end the read the
copy is parked on; the drain second, so that its own failure is observed
rather than abandoned; and the half-copied trace last, so nothing is still
writing into it when it is dropped.
The regression test pins the invariant rather than the symptom, because the
symptom is transport-specific: a Windows named pipe reports an aborted
overlapped read as cancellation, and a cancelled task is never unobserved, so
only the unix transport can produce the crash at all. What holds everywhere is
that no drain may still be running once the failure path is done with it.
Assisted-by: Claude:claude-opus-5:Claude Code
A refused unix socket raises SocketException, which derives from
Win32Exception rather than IOException, so it escaped the filter meant to
skip one unreachable process and failed the whole concurrent enumeration
instead: a machine where any .NET process exits between the port scan and
the connect showed an empty list. The classification is now a named
predicate covering both transports.
Two dialog defects shared a shape - state left behind by a query nobody is
waiting for any more. Rebuilding the bound collection on every filter
keystroke made the grid drop its selection and write that null back,
discarding the assemblies of a process the new filter still matched; and the
branch taken when nothing is selected cleared no loading flag, while the
query it superseded was no longer allowed to, so the progress bar animated
over an empty pane. Relatedly, the two-second command budget expired into a
process listed with null metadata, which made a slow machine look like a
runtime that answered with nothing; it is longer now, and expiry names the
process it gave up on, since the only thing that ever reaches the far end of
that budget is a runtime which will never answer.
The test gaps are closed the same way: a real dynamic assembly pins the
in-memory classification, the managed-only assertion is stated as the
property instead of a list of native names to exclude, and the .NET
Framework path gets live tests. Those showed that a desktop CLR process
mostly reports NGen native images rather than the IL assemblies behind them,
which is now recorded as the fidelity gap it is.
Assisted-by: Claude:claude-opus-5:Claude Code
With a modern .NET app the executable in the process list is a native
apphost that carries no IL, so the path a user can see is precisely the
one a decompiler cannot open - ILSpy itself is an example. Ask the
runtime instead: since .NET Core 3.0 every CoreCLR process serves a
diagnostics endpoint that names its managed entry assembly and, via an
EventPipe rundown, every assembly it has loaded, including ones behind a
single-file bundle or with no file at all. The endpoint answers the same
way on Windows, Linux and macOS and needs no privileges beyond same-user,
which also makes it the only workable route on macOS, where native
process introspection is gated by SIP.
The protocol and the nettrace container it returns are implemented here
rather than taken from Microsoft.Diagnostics.NETCore.Client, so the
feature costs no new package reference; the reader is scoped to loader
rundown events and steps over everything else by size. The rundown asks
for the loader keyword alone: the runtime's default set also collects the
JIT and IL-to-native-map rundown, which in a long-running process buries
the module list and overruns the session buffer, costing the very events
the dialog needs.
Windows additionally lists .NET Framework processes, which predate the
endpoint and are read from their OS module list instead.
Assisted-by: Claude:claude-fable-5:Claude Code
The decompiler suite is cross-platform code already exercised by the
Windows and Linux jobs; running it a third time on macOS only made the
slowest job of the matrix (~54 min vs ~41 min on Linux) slower without
adding coverage.
Assisted-by: Claude:claude-fable-5:Claude Code
The gate job was copy-pasted into build-ilspy.yml and build-frontends.yml
and the two copies had already started drifting (job casing, comment
wording). A workflow_call workflow keeps a single definition; the callers
shrink to a uses: job and route its "run" output to their build jobs
unchanged. Each caller must still grant pull-requests: read explicitly,
because the called workflow's token is the intersection of what the
caller grants and what the callee requests, and both callers default to
contents: read only.
Assisted-by: Claude:claude-fable-5:Claude Code
A branch with an open PR was built twice per push, once by the push
trigger and once by the pull_request trigger, doubling the Actions cost
of every PR iteration. The pull_request run is the canonical one (it
tests the merge with the base branch), so a small gate job now asks the
API whether the pushed branch has an open PR whose base would fire the
pull_request trigger and skips the push build if so. Pushes to master
and release/* are exempt because their runs publish packages; PRs
targeting other branches are ignored because they never trigger a
pull_request build that could replace the push one.
Filtering fork PRs in the on: section instead was considered and
rejected: trigger-level filters cannot see whether an open PR exists,
and skipping the pull_request runs would have lost merge testing.
Assisted-by: Claude:claude-fable-5:Claude Code
A bare `bash` on Windows can resolve to the WSL launcher
(System32\bash.exe) ahead of Git Bash, running the pre-commit hook
inside a Linux distro. There the CRLF-checked-out script fails to parse
("$'\r': command not found"), and even with clean line endings the WSL
path would install a Linux dotnet-format instead of the Windows one.
Resolve Git Bash from git.exe's install location instead, the same
CRLF-tolerant shell git itself uses to run the hook; non-Windows
platforms keep using plain bash.
Assisted-by: Claude:claude-fable-5:Claude Code
Each fixture test assembled Documentation/IdStringProbe.il separately,
and ilasm writes its output next to the input: on Windows the second
test failed with a sharing violation (0x80070020) because the first
test's PEFile still had the previous output open. The probe is now
assembled once per test run from a temp copy of the .il, so the tests
share one PEFile and nothing is written into the source tree, which
also makes the per-directory .gitignore unnecessary.
Assisted-by: Claude:claude-fable-5:Claude Code
IdStringMemberReference was its only implementation and left with the
ID string grammar parser; unlike the NRefactory-era type system, IMember
does not extend IMemberReference here, so nothing in the library
produces or consumes it anymore.
Assisted-by: Claude:claude-fable-5:Claude Code
A bound generic argument list without its closing brace fell off the
end of the string and silently produced an arity, making malformed ID
strings appear to parse and resolve to nothing instead of failing with
the documented ReflectionNameParseException. Also replaces the non-ASCII
punctuation in comments added by this branch with ASCII equivalents,
per the repository convention. Both raised by review on #3926.
Assisted-by: Claude:claude-fable-5:Claude Code
The C#/Roslyn-form ID of a member whose C++/CLI form differs can equal
the only key of a same-named sibling overload (char* vs signed char*).
Assemblies containing such overloads cannot come from the C# compiler,
so their xml files use the C++/CLI dialect, where that key documents
the sibling: falling back to the Roslyn form would show the sibling's
documentation for an undocumented member. GetIdStringCandidates now
omits the Roslyn form when a same-named sibling's C++/CLI form owns it,
so a lookup miss stays a miss.
Assisted-by: Claude:claude-fable-5:Claude Code
The recursive-descent parser for the full ID string grammar existed only
to feed the type-system-based FindEntity, while signatures were already
matched by regenerating candidate IDs; with two dialects a parser would
have to implement both grammars and stay in sync with the generator.
FindEntity now only decodes the structural skeleton - the declaring type
name and the member name - resolves the type, and narrows the members by
metadata name before the generate-and-compare step, falling back to an
unfiltered scan for keys whose name does not equal the metadata name
(a C++/CLI 'default' indexer). ParseTypeName, ParseMemberIdString and
their reference types (IdStringMemberReference,
GetPotentiallyNestedClassTypeReference) are removed.
Assisted-by: Claude:claude-fable-5:Claude Code
Member lookup compares regenerated ID strings, and with two dialects a
single mixed pass can resolve to the wrong member: the stripped
C#/Roslyn form of one member can equal the C++/CLI-dialect key of a
different member, e.g. C++ overloads differing only in a custom
modifier such as char* vs signed char*. FindMemberInType therefore
runs two passes over the whole member list, the more specific C++/CLI
form first, so an MSVC-written cref resolves to the member it names
instead of the first member whose stripped form happens to collide.
Assisted-by: Claude:claude-fable-5:Claude Code
The member keys in an xml doc file depend on the compiler that wrote it
(C# vs the MSVC C++/CLI dialect), so XmlDocumentationProvider's
entity-based lookup now tries each candidate form in order. The tooltip
path goes through the entity overload instead of building the key
itself, so the fallback lives in one place. The C++/CLI form is tried
first: wherever it differs it contains character sequences Roslyn never
writes, so it can only match MSVC-generated keys, while the stripped
Roslyn form of one member could match the key of a different member in
an MSVC-generated file.
Assisted-by: Claude:claude-fable-5:Claude Code
MSVC documents C++/CLI members with ECMA-372-style ID strings that
differ from Roslyn's in signatures: custom modifiers are rendered after
the modified type (a 'const int' parameter becomes
System.Int32!System.Runtime.CompilerServices.IsConst), arity markers
stay on generic instantiations (List`1{System.Int32}.Enumerator), and
the default indexed property is called 'default'. Roslyn ignores
modifiers entirely, so one generated string cannot match both
compilers' xml files: GetIdString keeps producing the C#/Roslyn form,
and the new GetIdStringCandidates additionally yields the C++/CLI form,
most specific first, for lookup code to try in order.
The dialect is pinned by IdStringProbe.il/.xml, the trimmed disassembly
of an MSVC-compiled probe assembly together with the unmodified xml MSVC
generated for it. Notable observed deviations from MSVC's documented
format: modreq is generated, but only modreq(IsVolatile) uses the
documented '|'; modreq(IsByValue) on conversion operator operands is
rendered with '!'.
Assisted-by: Claude:claude-fable-5:Claude Code
FindEntityInRelevantAssemblies now uses the metadata-level FindEntity
instead of parsing the ID string into type-system references and
resolving them per assembly. Two behaviors of the old path need
explicit handling because FindEntity only searches the modules it is
handed: reference assemblies are skipped so the search prefers an
assembly with a usable definition, and a member whose declaring type is
present only as a type forwarder is looked up in the assembly the
forwarder points to, which the assembly resolver may load on demand
(the old path got this through DecompilerTypeSystem resolution).
Assisted-by: Claude:claude-fable-5:Claude Code
Resolve an ID string to a (module, handle) pair by scanning metadata
directly: namespace/type-name splits are tried at every dot (the format
does not mark the boundary), nested types and type forwarders are
walked, and members are matched by regenerating each candidate's ID
string instead of parsing the signature portion, which keeps resolution
in sync with generation by construction. This gives navigation a
resolution path that needs no type system and works on assemblies
exactly as their metadata spells them. Inherent format limitations
(metadata names containing ID string special characters; function
pointer types rendering empty, so such overloads share an ID) are
documented on the method.
Assisted-by: Claude:claude-fable-5:Claude Code
Generate ID strings directly from metadata instead of the type system,
so callers holding only a MetadataFile and an EntityHandle do not need
to materialize a compilation first, and raw metadata names survive
verbatim. GetIdString(IEntity) stays as a thin wrapper over the new
implementation, keeping the published entity-based entry point intact.
The implementation is validated by a differential suite comparing every
symbol of a test corpus against Roslyn's GetDocumentationCommentId.
Corners pinned by those tests: generic arguments distribute to their
nesting level (Outer{A}.Inner{B}), op_CheckedExplicit carries the
~ReturnType suffix like the other conversion operators, and custom
modifiers are ignored like Roslyn ignores them (a virtual method's 'in'
parameter is modreq(InAttribute) but documented as T@). Array shapes
use the spec's lowerbound:size notation, covered by a hand-built
module, since C# cannot express non-default array bounds in signatures.
Assisted-by: Claude:claude-fable-5:Claude Code
* Make IProjectFileWriter implementations public and extensible
* Fix nullable error
* Fix delegate invocation to prevent race conditions
Refactored code to assign WriteCustomPropertyGroup and WriteCustomItemGroup delegates to local variables before null checks and invocation. This ensures thread safety by avoiding race conditions if the delegates are modified by other threads.
* Replace events with a GetCustomProperties virtual method
* Remove I prefix
With no configured solution, DotRush only auto-loads when the folder
contains exactly one solution file; this repo has several, so it shows
a picker and writes the chosen absolute path into .vscode/settings.json,
dirtying every fresh clone or worktree. The server resolves configured
entries against the workspace folder it is launched in, so a committed
relative path works everywhere and stops the write-back.
Assisted-by: Claude:claude-fable-5:Claude Code
Covers ref structs implementing interfaces (implicit, explicit, and
default-interface-method reimplementation, which CS9245 forces on every
ref struct implementer), the allows ref struct anti-constraint on
methods, classes, interfaces, delegates, local functions, iterators,
async methods, and capturing local functions, constraint combinations
(interface, IDisposable with using, unmanaged, struct, new()),
interface members invoked through a constrained T (instance, static
abstract factory, and default interface methods, which are callable
through T because implementers must always override them), scoped/ref/
in/out parameters of T, and call sites instantiating with Span/
ReadOnlySpan type arguments.
The test is green in all four Roslyn 4.14/latest debug/opt configs: the
decompiler already round-trips the gpAcceptByRefLike flag into
"allows ref struct" clauses and re-emits ref struct interface
implementations correctly. One cosmetic quirk is pinned as-is: calls
whose type arguments are ref structs are printed with explicit type
arguments and a declaring-type qualifier even within the declaring
class, because type-argument inference validation does not accept ref
struct type arguments; the output remains compilable and semantically
identical.
Assisted-by: Claude:claude-fable-5:Claude Code
The TypeDef table view derived FieldList/MethodList from the computed
member ranges (TypeDefinition.GetFields/GetMethods), and the MethodDef
view derived ParamList the same way; their FirstOrDefault is a nil
handle for an empty range, so memberless types and parameterless
methods displayed row 0. The stored column value is never 0: it is the
running list position, i.e. the next row's first member or one past the
member table's end. Read the raw columns instead, relative to the row
end so the widths of the preceding string-heap, blob and coded-index
columns need not be re-derived, and sized by the table the column
actually indexes (the FieldPtr/MethodPtr/ParamPtr indirection when
present). EventMap and PropertyMap already read their list columns from
the raw rows. Tooltips explain the empty-list-start semantics; rows
with members behave as before.
Assisted-by: Claude:claude-fable-5:Claude Code
Toggle folding picked the innermost fold containing the offset, but a
member's logical region is fragmented: the body fold starts at the
opening brace, the XML documentation has an independent fold, and the
header line belongs to neither, so toggling there collapsed the whole
enclosing type. Visual Studio's source editor keeps documentation
regions independent, but its metadata-as-source view treats the
member's leading trivia as one hideable unit; for a read-only
decompiler view the grouped behavior is the intuitive one.
The writer now records where an entity declaration begins, and the
definition's fold carries that logical start. Toggling targets the fold
whose logical region innermost-contains the offset, so the header line
targets the member rather than the type, and leading documentation
folds follow the member fold's new state. Inside the documentation the
doc fold itself is the innermost region and still toggles alone.
Toggle all folding now follows Visual Studio's Toggle All Outlining
parity: a mixed state expands everything, a uniform state flips.
Assisted-by: Claude:claude-fable-5:Claude Code
Clicking a local variable highlights its occurrences, but clicking a
member or type always navigated away, so there was no way to see all
uses of a member within the current view. With the new display setting
enabled (off by default, as discussed in the issue), a single click on
a member or type reference paints every occurrence in the view using
the local-reference highlight infrastructure, and Ctrl+Click performs
the navigation. Opcode references keep navigating on plain click.
Matching occurrences are compared by definition token and module
rather than by Equals, because a use site carries a specialized member
instance while the declaration carries the definition; this also fixes
the analyzer-driven highlight for specialized members. Unresolved
entity references from the IL and metadata views are compared
structurally to avoid building a type system per click.
The hand cursor promises navigation, so it is shown only when a click
would actually navigate: cursor queries factor in the setting and the
Ctrl state, and Ctrl presses/releases while hovering a reference
repaint the cursor via top-level key handlers, since keyboard focus is
usually elsewhere while hovering.
Assisted-by: Claude:claude-fable-5:Claude Code
Decompiling one method or property previously required decompiling its
whole type and searching the output, which is wasteful for scripted and
agent-driven use against large assemblies. The new -m|--member option
accepts an XML documentation id string (the syntax of compiler-generated
documentation files and of the UI's --navigateto option) or a metadata
token in 0x06000005 form, resolves it against the main module (with a
bounds check for tokens and distinct error messages for malformed ids,
unknown members, and members of other modules), and prints just that
member through the engine's single-member decompilation path.
The tests seed a new ICSharpCode.ILSpyCmd.Tests project (part of the
XPlat and Desktop solution filters and of the per-project CI test
steps), driving the real Main in-process via InternalsVisibleTo, so
future ilspycmd features have a dedicated home for CLI tests.
Assisted-by: Claude:claude-fable-5:Claude Code
The explicit 15-second budget timed out on a loaded Windows Release CI
runner (a different test of this fixture on each attempt, while the
sibling workflow run passed the same head). The waits are
completion-bounded - the predicate observes the end of an async
re-decompile - so the 60-second Waiters default costs nothing when
healthy and only converts runner slowness from a red run into a longer
green one.
Assisted-by: Claude:claude-fable-5:Claude Code
Filtering and un-filtering the Debug Steps tree re-arranges rows around
the selection: matches surface under newly expanded groups and the
restore step re-opens the selected step's ancestors. Without scrolling,
the selected row regularly ended up outside the viewport even though it
was expanded into view. The ViewModel now raises a reveal request after
each filter pass while a visible step is selected; the View answers it
by centering the row, because scrolling needs containers and the
ScrollViewer. The measurement anchors on the row header since an
expanded group's container spans its whole subtree, and it runs at
Loaded dispatcher priority so freshly expanded containers have valid
geometry.
Assisted-by: Claude:claude-fable-5:Claude Code
The filter drove every row's IsExpanded from the single IsFiltering flag
via a TreeViewItem style setter. Expansion lived nowhere else: user
gestures (expander arrow, double-tap, arrow keys) all write the property
with SetCurrentValue, which the style binding overwrites on every flip
of the flag. Starting a filter therefore destroyed the expansion state
the user had built up, and clearing it collapsed the whole tree,
burying the still-selected row under collapsed groups.
Row state (visibility + expansion) now lives on a StepNodeViewModel
wrapper per step, two-way bound from the style, so gestures persist in
the view-model. A filter session snapshots expansion on entry, hides
non-matches and opens only the paths to matches while typing, and on
exit restores the snapshot and re-expands the selected step's ancestors
so the selection stays visible. Wrapping is skipped for reference-equal
step lists because step replays re-report the same run and a rebuild
would wipe the state mid-navigation.
Assisted-by: Claude:claude-fable-5:Claude Code
The netcore-2.2 reference set consists of the shared framework's facade
assemblies split across many files, and vbc only binds special types
like System.Void from an assembly that defines them rather than
following type forwards, so without an implicit SDK it needs the same
reference list as the C# side. The VB runtime must come from the legacy
reference set: before .NET Core 3.0 there is no
Microsoft.VisualBasic.Core.dll and the core build of the VB runtime is
a trimmed-down subset (no UBound etc.). Referencing the target
framework's own Microsoft.VisualBasic facade alongside that -vbruntime
choice is a BC32210 identity conflict, so it is dropped from both the
default reference list and the ReferenceVisualBasic flag handling.
All of this applies only where vbc runs without its implicit desktop
SDK path, i.e. off Windows; on Windows vbc.exe keeps the plain
reference list that already worked.
Assisted-by: Claude:claude-fable-5:Claude Code
The Roslyn 1.3.2 and 2.10.0 configurations were excluded from the
compiler matrix on non-Windows platforms because Microsoft.Net.Compilers
only ships .NET Framework executables. Both can be enabled:
- Roslyn 2.10.0 has a dotnet-hosted sibling package,
Microsoft.NETCore.Compilers, whose tools/bincore/csc.dll runs on the
installed runtime with --roll-forward LatestMajor (its runtimeconfig
pins the out-of-support .NET Core 2.0). Fetched on non-Windows into
the version's tools/bincore directory; GetCSharpCompiler probes for a
direct csc.dll next to the installed path in addition to the
bincore/ subfolder layout of the newer toolset packages.
- Roslyn 1.3.2 has no .NET build; when a mono executable is found on
the PATH, it is kept in the matrix and WrapCompiler hosts the .exe
compilers through mono. Because the native DiaSymReader needed for
Windows PDBs is unavailable there, GeneratePdb requests portable
PDBs on non-Windows (Roslyn 2.x+ falls back on its own, 1.x needs
the explicit -debug:portable).
The mcs configurations stay excluded: the bundled mcs 2.6.4 needs the
Reflection.Emit COMPILER_ACCESS mode that current Mono runtimes no
longer implement. Old-compiler configurations also stay excluded from
correctness-style fixtures: their output targets .NET Framework or
.NET Core 2.2, which the runners cannot execute here.
Microsoft.NETCore.Compilers-2.10.0.nupkg should be added to
ILSpy-tests/nuget to keep the fetch offline-capable.
Assisted-by: Claude:claude-fable-5:Claude Code
The override modifier already navigates to the overridden member and
constructor initializers link this/base to the invoked constructor, but
the primary expressions carried no reference at all. Matching IDE
go-to-definition behavior, 'this' now references the current type and
'base' the base type; both directions are added together deliberately,
linking only 'base' would make the two keywords behave inconsistently.
Assisted-by: Claude:claude-fable-5:Claude Code
The analyzer panel could only walk downward (Overridden By, Implemented
By); finding what a member overrides required manually climbing base
types. The upward interface direction already exists as the Implements
analysis, so this adds the missing base-class direction as its dual.
The analyzer walks the override chain member by member via
InheritanceHelper.GetBaseMember instead of collecting all
signature-equal base members, so it stops at 'new virtual' shadow
boundaries, which hide any further base members from overriding. No
assembly-scope scan is needed; the walk is a direct type-system lookup.
Assisted-by: Claude:claude-fable-5:Claude Code
The chain cases in the fixture route T : TOuter through a class-level
type parameter; the variant where the dependency target is a sibling
method type parameter (M<T, U> with T : U) was uncovered. It pins the
same alignment from a different angle: csc rejects 'class' with CS8665
when U is merely class-constrained and requires 'default', but accepts
'class' when U carries a class-type constraint, matching what the
tri-state IsReferenceType derives. Both directions were verified
against csc before adding the expected output.
Assisted-by: Claude:claude-fable-5:Claude Code
Corrupt or hand-written metadata can contain a property with no
MethodSemantics rows at all. MetadataTypeDefinition.Properties already
skips properties with neither a visible getter nor setter, so neither
the parameterized-property path nor the ordinary one ever sees them;
the accessor-method emission also tolerates a missing accessor by
construction. Pin that with an ILPretty case containing both a
parameterized and an ordinary accessor-less property.
Assisted-by: Claude:claude-fable-5:Claude Code
The renamed-Implements case exposed a gap: the accessor-method
declarations did not include the explicit-interface-implementation
forwarders generated from .override directives, so decompiled types did
not implement their interfaces and same-name implementations lost their
interface mapping. DecompileParameterizedProperty now emits the same
forwarder stubs as the ordinary method path.
Assisted-by: Claude:claude-fable-5:Claude Code
The main tree, tooltips, and search results once showed the parameter
list of a parameterized property; the ambience lost that when property
rendering went through the converted AST node, whose C# property syntax
cannot carry parameters. Take the parameter list from the symbol
instead and render it in parentheses (matching VB.NET usage syntax and
distinguishing these properties from indexers).
Assisted-by: Claude:claude-fable-5:Claude Code