Fix #3804: decompile foreach over inline array#3837
Conversation
The C# compiler lowers a foreach over an inline array into a for loop whose body calls <PrivateImplementationDetails>.InlineArrayElementRef(ref b, i) with the loop variable as index. MatchInlineArrayElementRef only recognized a constant index, so this call survived into the output verbatim; because <PrivateImplementationDetails> cannot be named in C#, the result did not compile. Accept a non-constant index in the matcher. The buffer-length range check only applies to a compile-time-constant index; the compiler emits this helper only where the index is provably in range. Everything downstream (LdElemaInlineArray, the indexer rendering) is already index-agnostic, so the body recovers to the inline-array indexer b[i], producing a valid for loop. The decompiler does not reconstruct the original foreach, but the output now compiles. Assisted-by: Claude:claude-opus-4-8:Claude Code
There was a problem hiding this comment.
Pull request overview
Fixes a C# decompilation bug where foreach over an inline-array value could decompile into a reference to the compiler-internal <PrivateImplementationDetails>.InlineArrayElementRef(...), producing non-compilable C# due to the angle brackets in the type name. The change updates the inline-array helper matcher so the call is rewritten into the inline-array indexer form (e.g. b[i]) even when the index is not a compile-time constant, and adds a regression test.
Changes:
- Relax
InlineArrayElementRefpattern matching to accept non-constant index expressions while keeping bounds validation for constant indices. - Add a Pretty test case that compiles a
foreachover an inline array and expects decompilation into a compilableforloop usingb[i].
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| ICSharpCode.Decompiler/IL/Transforms/InlineArrayTransform.cs | Allows matching InlineArrayElementRef with non-constant indices (e.g. loop variable from foreach lowering), enabling rewrite to ldelema.inlinearray and ultimately b[i]. |
| ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs | Adds a regression fixture using the EXPECTED_OUTPUT idiom to validate decompilation of foreach over an inline array into a compilable loop. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
This changes the behavior with an out-of-bounds non-constant index. This transform is only valid if the decompiler can prove the index is in-bounds. |
Fixes #3804.
Problem
Decompiling a
foreachover an inline-array value emitted code referencing the compiler-internal<PrivateImplementationDetails>.InlineArrayElementRefhelper, whose type name contains angle brackets and cannot be written in C# — so the output did not compile.Root cause
The C# compiler lowers
foreachover an inline array into aforloop whose body calls<PrivateImplementationDetails>.InlineArrayElementRef(ref b, i)with the loop variable as the index.InlineArrayTransform.MatchInlineArrayElementRefonly recognized a constant index (LdcI4), so the call survived into the output verbatim.Fix
Accept a non-constant index in the matcher. The buffer-length range check only applies to a compile-time-constant index; the compiler emits this helper only where the index is provably in range. Everything downstream (
LdElemaInlineArray, indexer rendering) is already index-agnostic, so the body recovers to the inline-array indexerb[i], producing a validforloop. The decompiler does not reconstruct the originalforeach, but the output now compiles.Test
Adds a
Sum(Byte16 b)method to theInlineArrayTestsPretty fixture. Because the fixture is both input and expected output, it uses theEXPECTED_OUTPUTidiom: theforeachis compiled (to trigger the lowering), and theforloop overb[i]is the expected decompilation. Verified against allroslyn4OrNewerOptionsconfigs.🤖 Generated with Claude Code