Commit Graph
4999 Commits
Author SHA1 Message Date
Siegfried Pammer 4a0918cbe5 Look past comment placeholders when matching statement sequences
The decompiler emits comments - //IL_ warnings, "Could not convert
BlockContainer", "try-fault", a Nop's comment - as an EmptyStatement in the
middle of a statement sequence. Every transform that walks such a sequence
then stops recognizing its pattern the moment one of those lands in it:
constructor initializers stay in the body, `using var` and `for` are not
introduced, and a finalizer keeps its `override Finalize` shape, which does
not compile at all.

The destructor matcher moves the placeholders it skipped into the body that
replaces the old one, so the warning that caused the problem is not dropped
along with the statement carrying it.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-10 20:01:19 +02:00
Siegfried Pammer 46d21ed9a5 Accept a deconstructed backing-field store only without a setter
A constructor store to an auto-property's backing field is expressible
after the field declaration is gone in one of two ways: ReplaceBackingFieldUsage
rewrites it to an assignment of a setter-less property, or
TransformFieldAndConstructorInitializers lifts it into a property initializer.
A deconstruction target assigns several members at once, so it can never take
the second route, and a property that kept a setter would invoke that setter
instead of storing the field. Without the restriction the declaration is
removed while the store keeps referencing it.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-10 18:16:16 +02:00
Siegfried Pammer addf4ce1a7 Merge pull request #4127 from icsharpcode/tests/autoproperties-nullable
Ignore nullability when comparing backing field and property type of auto properties
2026-09-09 19:10:56 +02:00
Siegfried Pammer ebb8697e13 Ignore nullability when comparing backing field and property type of auto properties 2026-09-09 18:38:57 +02:00
Siegfried Pammer 2e9d137e30 Merge pull request #4124 from icsharpcode/fix/2823-async-catch-handler
Fix #2823 and add a PDB verification mode to nugetfuzz
2026-09-09 09:47:25 +02:00
Siegfried Pammer 4e2f28109e Fix #2823: write the async catch handler offset the way consumers read it
The async stepping blob's first field is the compiler-generated catch
handler's IL offset plus one, and 0 when there is nothing to record. ILSpy
wrote the raw offset, so a consumer decoding it as (value - 1) resolved an
address in the middle of an instruction: Mono.Cecil throws
ArgumentNullException while reading the body, which is why the reported
assembly opened in ILSpy but killed ILLink on every MoveNext it had.
CatchHandlerOffset now holds the offset it is named after, or -1 for "none",
and BuildBlob applies the bias - the same model the compiler uses.

It is recorded only where an escaping exception is unlikely to be observed:
an async void method, and an async entry point. Recording it more widely
would be worse than recording it nowhere, because an async Task method
returns its exception through the Task and the debugger would then break on
exceptions user code catches. Measured over csc 1.3.2 to 5.10: async void is
handler+1 and a normal async Task is 0 in every version; only async Main
changed, from 0 to handler+1 between 2.10 and 3.11.

The entry point token names the synchronous '<Main>' shim that exists because
the runtime will not take .entrypoint on an async method, so the method to
record is the one the shim calls. Reading that call is exact; matching the
shim's siblings by name is not, and gets a 'Main' overload beside the real
entry point wrong.

Both tests compare against the compiler's own PDB for the same assembly, so
the blobs describe the same IL and the field compares directly.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-09 09:11:27 +02:00
Christoph Wille 18027c86bb Merge pull request #4125 from icsharpcode/christophwille/nu0926
September 2026 NuGet Updates
2026-09-09 08:15:36 +02:00
Christoph Wille 067122455a NuGet September 2026 Updates including .NET 11 RC1 with nearest Roslyn 2026-09-09 07:15:11 +02:00
Siegfried Pammer d09e3c1f67 Merge pull request #4111 from icsharpcode/fix/3568-record-member-order
Fix #3568: read a record's member order off its generated members
2026-09-08 06:42:27 +02:00
Daniel GrunwaldandSiegfried Pammer e686819617 Remove accidental blank line
Co-authored-by: Siegfried Pammer <[email protected]>
2026-09-06 15:24:23 +02:00
Siegfried Pammer 0e6b9a7e26 Fix #3568: read a record's member order off its generated members
The order of a record's fields and properties has to be known, because
Equals, GetHashCode, PrintMembers and the copy constructor are recognised
by walking their bodies in lockstep with it. It was assumed to be every
property followed by every field, so a record that declares a field
before a property desynchronised all four at once: none was recognised as
generated, all of them were emitted, and the auto-properties lost their
backing fields to raw <Property>k__BackingField accesses - output that
does not compile.

The order is in the generated members themselves, but no single one has
all of it: Equals compares everything that carries state and never a
computed property, PrintMembers prints everything public and never a
private field. Both follow declaration order, so the two sequences are
merged along the members they share, which puts a private field and a
computed property back in the right places relative to each other.
Members neither of them mentions - EqualityContract, static members -
keep the position they had.

Where the two orders conflict, which can only happen for a member that
one of them never sees, the equality order wins; nothing in the metadata
says more, and the choice cannot change more than the order the members
are printed in.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-06 08:23:00 +02:00
Daniel Grunwald 09203e058a Delete ProjectedList 2026-09-05 12:51:44 +02:00
Daniel Grunwald 8ac7963b25 Remove ITypeInference (dead code) 2026-09-05 12:39:57 +02:00
Siegfried Pammer ad08d21884 Merge pull request #4106 from icsharpcode/perf/resolver-file-system-access
Read the file system once per reference closure, not per reference
2026-09-05 10:07:01 +02:00
Siegfried Pammer 2115bd9028 Read the file system once per reference closure, not per reference
Resolving one assembly resolves its whole reference closure, and every
reference in it asked the same framework directories the same questions.
The worst of it was the scan for the closest version folder of a shared
framework: a directory listing plus a recursive file search, repeated per
reference and per runtime pack - 42 scans for two distinct answers when
decompiling ICSharpCode.ILSpyX.dll.

The scan result is only safe to keep for a bounded time: a runtime can be
installed or removed while ILSpy runs, and reloading an assembly list has
to see that. So it is kept for the length of an explicitly opened scope,
which the type system opens around the closure it resolves and closes
again afterwards; outside a scope the file system is read as before. The
scope owns what was read, so two of them on one resolver do not stack -
the first to end takes it, and the other reads the file system again.

BeginSnapshot is on IAssemblyResolver rather than an interface of its
own: it is core functionality of a resolver, and one implementation is
not an abstraction. This breaks the interface for implementors outside
this repository, who opt out by returning null - which is what the three
resolvers here that hold nothing do.

The remaining probes cost nothing to fix: the preferred runtime pack was
listed among the defaults it already belongs to, so its directory was
scanned twice for every reference that is not in it, and one package
folder was probed once per assembly the package contains.

Measured over 27 references with a fresh resolver each time: 3.3 ms per
assembly before, 3.1 ms without a scope, 1.1 ms with one.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-04 19:55:39 +02:00
Siegfried Pammer c6c6831200 Write item metadata the way a non-SDK project keeps it
Metadata as attributes on an item element is MSBuild 15 syntax. The
non-SDK project format is what an export falls back to for toolchains
that predate the SDK, and those reject an unknown attribute on an item
element, so a Page item carrying Generator and SubType as attributes
undoes the reason to write that format at all. Every non-SDK project
written by anything else keeps metadata in child elements.

The SDK-style writer keeps attributes: there the syntax is a given and
it is what the format's own tooling produces.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-04 19:41:19 +02:00
Siegfried Pammer bff069a9e2 Put a XAML document where its code-behind is
The project exporter wrote every XAML document to the project root under
a fully-qualified name while the code-behind class went into a directory
named after its namespace, so the two halves of one partial class ended
up in different places. WPF tooling pairs MainWindow.xaml with
MainWindow.xaml.cs by name and location; anything else is an unrelated
file to it, and --nested-directories made the split wider still by moving
only the C# half.

Both now go through one function that decides where a type's files live,
so the document lands where the type's own C# file would have, and the
code-behind is named after the document. The BAML writers of the UI and
of the command line had grown their own copies of the naming, which is
how they came to disagree with the C# writer in the first place.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-04 19:41:19 +02:00
Siegfried Pammer a6efe758c8 Fix #2080: resolve GAC references onto the installed version
The GAC probe only ever looked for the exact folder of the requested version.
For about a hundred assemblies the .NET Framework 4.7.2/4.8 reference assemblies
carry a higher version than the implementation ever installed in the GAC
(System.IO.Compression is 4.2.0.0 against 4.0.0.0 in the GAC, System.Runtime is
4.1.2.0, ...), because out-of-band packages shipped those versions and the ref
assemblies had to keep up. The runtime hides this behind assembly unification;
without an equivalent, every reference to one of them was reported as
unresolvable.

Matching on the major version keeps assemblies apart that share a name but are
different products, e.g. Microsoft.Build.Framework 4.0.0.0 and 15.x.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-04 08:18:17 +02:00
Daniel Grunwald 56f45ae8f3 Merge pull request #4053 from sonyps5201314/split/unresolved-value-type-call-targets
Handle unresolved value-type call targets
2026-09-03 21:21:58 +02:00
Siegfried Pammer 0b7fa142d7 #3315: Gate implicit references the way the SDK gates them
The exporter dropped PresentationFramework, System.Xaml, System.Windows.Forms
and System.Drawing from every project it wrote, whatever the assembly used,
while a second list held the remaining WPF assemblies behind a WPF check. The
SDK draws the line elsewhere: Microsoft.NET.Sdk.WindowsDesktop.props promotes
the nine _WpfCommonNetFxReference items to _SDKImplicitReference only when
UseWPF is set, System.Windows.Forms only when UseWindowsForms is, and
WindowsFormsIntegration only when both are. A XAML-only assembly therefore lost
a reference that nothing supplied, and a WPF application that also used Windows
Forms lost the Windows Forms references while the project only said UseWPF.

Following the SDK there makes WPF and Windows Forms independent rather than
alternatives, which is what the flags enum is for: an assembly can use both,
and then both properties have to be written. Where an assembly looks like more
than one kind of project, the web SDK wins the Sdk attribute, because
Microsoft.NET.Sdk.Web imports Microsoft.NET.Sdk and so carries the desktop
targets, while Microsoft.NET.Sdk.WindowsDesktop carries no web targets.

System.Drawing stays unconditional: Microsoft.NET.Sdk.BeforeCommon.targets adds
it for every .NETFramework target rather than only for Windows Forms ones, and
on .NET Core it ships in the Microsoft.NETCore.App reference pack.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 20:38:55 +02:00
Siegfried Pammer d93bbcdb73 #3315: Drop references that UseWPF already supplies
An exported WPF project listed PresentationCore next to the implicit
Windows Desktop framework reference, which is a duplicate reference
(MSB3243) or an unresolvable one (MSB3245) once the hint path stops
pointing anywhere. The target-pack filter that should have caught it
asks the assembly resolver, which answers by probing the shared
frameworks installed on the machine running the export - so the same
assembly exported from Linux, or from a Windows box without the
desktop runtime, produced a different project file. What the SDK adds
for UseWPF is a fixed list, so match it by name instead.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 20:32:18 +02:00
Siegfried Pammer 2900d18bca #3315: Ask for the WindowsDesktop SDK only where it is still needed
Microsoft.NET.Sdk imports the Windows Desktop targets itself for .NET
Framework and for .NET 5 and later, and warns (NETSDK1137) about every
project that still names the separate SDK. Only .NET Core 3.x, where
those targets are not imported without a platform-suffixed moniker,
genuinely needs Microsoft.NET.Sdk.WindowsDesktop.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 20:32:18 +02:00
Siegfried Pammer fe8477e943 #3315: Keep the target platform in exported target framework monikers
A .NET 5 or later project that sets UseWPF or UseWindowsForms is
rejected outright (NETSDK1136) unless its target framework names the
Windows platform, so an exported WPF assembly produced a project that
could not build at all. The platform belongs to the assembly rather
than to WPF - TargetPlatformAttribute records it, SupportedOSPlatform
its minimum version - so the moniker follows the attributes wherever
they are present, and falls back to plain "windows" only for a desktop
project built before those attributes existed. Monikers older than
net5.0 take no platform suffix and must not grow one.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 20:32:18 +02:00
Siegfried Pammer 1534a051fe #2253: Export the WPF application definition as such, not as a Page
The WPF markup compiler generates the program entry point from the
ApplicationDefinition item, so an exported project that lists App.xaml
as a Page has no Main at all and fails to build with CS5001. Both the
UI and ilspycmd already resolve the BAML root's partial class, which
makes deriving Application from System.Windows.Application the natural
signal. The module additionally has to have an entry point of its own:
a library that merely contains an Application subclass would otherwise
have MSBuild generate a Main into it.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 20:32:18 +02:00
Daniel Grunwald 9377648cba Merge branch 'master' into split/unresolved-value-type-call-targets 2026-09-03 20:29:06 +02:00
Siegfried Pammer d9e444b6ca Merge pull request #4102 from icsharpcode/fix/wpf-resource-logical-names
Fix #3315: make exported WPF projects reproduce their resources
2026-09-03 19:27:23 +02:00
Siegfried Pammer 7873fd5481 Merge pull request #4100 from icsharpcode/fix/3008-callbuilder-assert
Fix #3008: local function forwarding a display class loses its scope
2026-09-03 19:19:52 +02:00
Siegfried Pammer d77a86f593 #3315: Give colliding resource names separate files on export
Sanitizing a resource name is not injective: "a+b/logo.png", "a&b/logo.png"
and "a#b/logo.png" all come out as "a-b/logo.png". The writers create files
with FileMode.Create, so every colliding entry but the last was lost, and
nothing was written to the error list to say so - an assembly can be built
to make that happen to as many entries as it likes. A WPF probe assembly
with 20 resource entries exported as 13 files.

Uniquifying is enough because the exported item already pins the true name
in its LogicalName, so the file on disk has to be unique, not faithful. The
suffix search resumes where the previous collision on a name left off, so a
crafted pile of collisions stays linear rather than quadratic, and the name
is trimmed to keep the segment within the file system's limit.

Directory creation moves inside the per-entry error recovery for the same
reason: an entry named after a directory another entry needs makes it throw,
and that has to cost the one entry rather than the rest of the container.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 12:08:33 +02:00
Siegfried Pammer c0626ada56 #3315: Pin WPF resource IDs when exporting a project
A rebuilt WPF project only resolves its own pack URIs when every entry of
"<AssemblyName>.g.resources" comes back under the resource ID it had before.
The file on disk cannot carry that ID: it is sanitized for the file system,
and the ID itself is escaped. Verified against a WPF assembly built for this:
the WPF build tasks re-escape whatever LogicalName they are given, so the item
has to hand them the decoded name, and an entry left as EmbeddedResource
rebuilds into a manifest resource of its own instead of into ".g.resources".

The adjustment is made where the items are collected, so it covers the base
class and both hosts that plug their own BAML handling into it without
widening the WriteResourceToFile or IResourceFileHandler contracts.

The Resource build action this gives them is also what #2253 asks for.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 12:08:21 +02:00
Siegfried Pammer 65092d8bd8 #3315: List all decompiled project items in SDK-style projects
The SDK-style writer only ever emitted EmbeddedResource items, so every other
item type the export produced never reached the project file. XAML recovered
from BAML lands in Page items: ILSpy wrote the .xaml files to disk and the
project referenced none of them, leaving an exported WPF project that cannot
rebuild.

Explicit items collide with the SDK's own globs - a UseWPF project globs
**/*.xaml into Page, and NETSDK1022 rejects the duplicate - so each include is
preceded by a remove of the same item type, the pattern the EmbeddedResource
path already uses. Setting EnableDefaultPageItems=false would work as well, but
it is WPF-specific and switches off a glob for the whole project, whereas the
remove is per item, applies to any item type, and keeps the SDK's item
definitions (XamlRuntime) in effect.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 12:08:09 +02:00
Siegfried Pammer e770bec382 Fix #3315: decode URI escapes in WPF resource names
WPF's build tasks key every Page and Resource item in
"<AssemblyName>.g.resources" by the item's relative path, lower-cased and
run through Uri.GetComponents(Path, UriEscaped) - so a folder named
"Resource Test" arrives as "resource%20test", and a folder named in any
non-ASCII script arrives as a run of UTF-8 percent escapes. Those escapes
are not part of the name; sanitizing them turned "resource%20test" into
"resource-20test" and any Chinese or umlaut folder into a line of hex.

Measured against a WPF assembly built for this: only space, '#', '{', '}'
and non-ASCII bytes are ever escaped, and Uri.UnescapeDataString is the
exact inverse - anything the escaper leaves alone contains no percent
sign, and a literal one arrives as %25. Only the WPF-generated containers
are affected, so a percent sign in any other .resources file stays part
of the name.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-03 12:07:58 +02:00
Daniel Grunwald 5667f7582e Merge pull request #4090 from icsharpcode/infer-type
Extend InferType() to every ILInstruction
2026-09-03 07:31:31 +02:00
Siegfried Pammer f007528af9 Fix #3008: local function forwarding a display class loses its scope
Roslyn passes a display class into a local function by ref, and a local
function that only forwards that parameter to a sibling has no closure
variable of its own. The closure analysis therefore found nothing to
anchor it and fell back to the root method body, which put it out of
reach of the callees it forwards to; CallBuilder then hit the assert
guarding a local function reference it cannot resolve and emitted the
raw metadata name of the target instead.

The constructor path also mixed use-site containers into a scope the
closure analysis had already determined; when the use-sites live in
separate function bodies there is no common container, and resetting to
the constructor body threw that scope away.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-02 00:12:30 +02:00
Siegfried Pammer 9f6f90ec7a Merge pull request #4098 from icsharpcode/fix/2054-facade-forwarder-resolution
Fix #2054: repair a chain of type forwarders that cycles
2026-09-01 21:47:16 +02:00
Siegfried Pammer af941339c0 Fix #2054: repair a chain of type forwarders that cycles
A chain of type forwarders is followed by assembly name, and every name resolves relative
to the assembly being decompiled - so a chain that leaves for another framework can be
pulled straight back into the directory it started in. A .NET Standard 2.0 assembly
sitting among .NET Framework 4.6.1 facades lost System.Linq.Enumerable that way: the
chain went netstandard -> System.Core (from the shared framework) -> System.Linq (back to
the input directory) -> netstandard, arriving at an assembly it had already passed
through. Nothing in the closure defines the type, so it stayed unknown and every LINQ
call decompiled as a static call with a delegate cast.

Once the closure is loaded, chains that return to an assembly they already visited are
walked a second time, resolving each hop next to the assembly that forwards it. The
assembly ending the repaired chain is loaded only once it is confirmed to declare the
type; it then wins the deduplication against the assembly of the same name it displaces,
which version order says nothing about. An assembly that neither forwards nor declares
the type ends the walk with nothing loaded, so a failed repair cannot displace anything.

Only chains that are already broken are walked twice. Preferring the forwarder's own
directory as a resolution policy was tried first and rejected: measured against a corpus,
it moved a .NET 8 facade's System.Runtime reference out of a net4x compilation, splitting
type identities so that overrides printed as virtual. The two cases cannot be told apart
where references are resolved, because that layer sees assembly names, not the type whose
chain is or is not terminating.

AssemblyReference now knows the module that declares it, which is what lets a hop be
resolved next to its forwarder, and its metadata reader is that module's.

A chain that cannot be repaired is reported in the reference load log the UI already
shows, once per reference: a facade forwards hundreds of types and they all fail together.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-01 12:20:56 +02:00
Siegfried Pammer bb1d830ee6 Fix #2040: escape BAML strings that XML cannot represent
Obfuscators put arbitrary characters into BAML strings, and XML 1.0 has no
representation for most control characters - a numeric character reference is invalid
for them too. Writing such a document threw ArgumentException from XmlWriter, which
loses the resource on project export and shows an exception instead of the page in the
UI. The escapes are spelled the way the C# output spells them, so one convention covers
both languages. Namespace URIs have to be escaped where the XNamespace is created rather
than in the final pass: the URI is baked into every element name built from it, so
patching only the xmlns declaration would desync the two. Characters XML can carry stay
untouched, so ordinary documents decompile byte-identically.

Every BAML stream of an assembly lives in one .resources container, and the recovery
around resource writing sat outside the loop over its entries, so a single page that
could not be written discarded every other page sharing the container with it.

Assisted-by: Claude:claude-opus-5:Claude Code
2026-09-01 06:50:11 +02:00
Siegfried Pammer 9aa3911f3a Remove definition-chasing pointer recovery in DeconstructInstruction.IsAssignment
GetPointerElementType existed because a pointer passing through a stack
slot could be typed IntPtr: ILReader replaced the slot type with
FindType(StackType) whenever the inferred type did not match the stack
type. With InferType() implemented on every ILInstruction that fallback
is gone (FlushExpressionStack now asserts the inferred type is
stack-accurate), so the target's inferred type is precise and the
definition chain no longer needs to be walked. Merged stack slots were
never recovered by the helper anyway (it required a single store).

Disabling the PointerType arm makes the uint*/byte* deconstruction
fixtures fail, so the sign-agnostic stobj.Type fallback remains guarded.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-08-30 22:55:07 +02:00
Daniel Grunwald 124d9116d6 Fix assertion with v?.NonVoidCall(); in a context where the result of the call was ignored. 2026-08-30 22:04:17 +02:00
Daniel Grunwald 8fa1fd76dc Relax SwitchInstruction invariant: throw-expression branches don't need to agree in the type. 2026-08-30 21:37:35 +02:00
Daniel Grunwald 76f67f3fca Add DetectImprovedTypeForForBitOp as a transform step.
Previously this happened within the InferType implementation.
2026-08-30 21:27:50 +02:00
Daniel Grunwald 530a214b21 Relax NullCoalescingInstruction invariant. 2026-08-30 21:27:50 +02:00
Daniel Grunwald 5526400b17 Extend InferType() to every ILInstruction
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`.
2026-08-30 21:25:45 +02:00
Daniel Grunwald cf4dba7015 Always pass the type system into InferType(). 2026-08-30 19:18:47 +02:00
Daniel Grunwald 123da4deeb #nullable enable for TransformAssignment. 2026-08-30 19:18:46 +02:00
Daniel Grunwald 0927cde0d5 Merge pull request #4057 from icsharpcode/fix/lambda-parameter-syntax
Fix uncompilable lambda parameter lists
2026-08-30 15:12:54 +02:00
Siegfried Pammer 3e45005ebd Emit params and default values on lambda parameter lists
C# 12 allows both on the explicitly typed parameter list of a lambda, and
nowhere else: an anonymous method cannot declare either, and neither can a
lambda whose parameter list is about to be dropped. Guarded by a setting so
the output stays valid for earlier language versions.

Only what the anonymous function's own metadata declares is written. A lambda
may state a default the delegate does not have, a different one, or none where
the delegate has one, and reflection over the lambda's method reports what the
lambda declared - so filling either in from the delegate's Invoke would make
the recompiled assembly describe itself differently from the original. Call
sites are unaffected either way, because they bind against the delegate, which
still declares both.

Roslyn writes ParamArrayAttribute on the anonymous function's own method only
from version 5 on; before that it stands on the delegate type alone, where it
is not the lambda's to restate, so the fixture guards those cases on ROSLYN5.
The correctness test reads the metadata back through reflection, which is the
only place the difference is observable.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-08-30 13:49:33 +02:00
Siegfried Pammer 0ef295947b Parenthesize lambda parameter lists when attributes are present
The C# 10 grammar only allows attributes on a lambda or its parameters
when the parameter list is parenthesized, but LambdaNeedsParenthesis
predates attribute support and only considered the single parameter's
type and modifiers. An attributed lambda whose parameter type is erased
for being anonymous therefore printed as '[My] a => a.X', which does not
parse. Latent since attributed-lambda decompilation was added: every
other attributed lambda has explicitly typed parameters, which already
force the parenthesized form.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-08-30 13:49:10 +02:00
Daniel Grunwald a9acf77193 Merge pull request #4086 from icsharpcode/fix/ref-conditional-assignment-parens
Parenthesize a ref-conditional assignment target
2026-08-30 13:27:07 +02:00
Siegfried Pammer 32945861cf Merge pull request #4085 from icsharpcode/il-checkinvariant-compilation
Pass the compilation to ILInstruction.CheckInvariant
2026-08-30 13:06:40 +02:00
sonyps5201314 d730ec0a20 Handle unresolved value-type call targets
A missing value-type definition makes ILReader insert a Ref-to-Unknown conversion before instance calls. Preserve the managed-reference receiver so C# output does not fall back to invalid ref casts or unsafe pointers.

Assisted-by: Codex:gpt-5.6-sol:Codex
2026-08-30 19:02:06 +08:00