diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
index 57049c08e..537db46ad 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
@@ -99,6 +99,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return array[GetIndex()] = (array[GetIndex() + 1] = value);
}
+ public int SumForeach(Byte16 b)
+ {
+ int num = 0;
+ foreach (byte b2 in b)
+ {
+ num += b2;
+ }
+ return num;
+ }
+
public void OverloadResolution()
{
Receiver(GetByte16());
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
index a321808ff..585df5049 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
@@ -93,6 +93,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override AstNode VisitForStatement(ForStatement forStatement)
{
AstNode? result = TransformForeachOnArray(forStatement);
+ if (result != null)
+ return result;
+ result = TransformForeachOnInlineArray(forStatement);
if (result != null)
return result;
return base.VisitForStatement(forStatement);
@@ -402,6 +405,112 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return foreachStmt;
}
+ static readonly ForStatement forOnInlineArrayPattern = new ForStatement {
+ Initializers = {
+ new ExpressionStatement(
+ new AssignmentExpression(
+ new NamedNode("indexVariable", new IdentifierExpression(Pattern.AnyString)),
+ new PrimitiveExpression(0)
+ ))
+ },
+ Condition = new BinaryOperatorExpression(
+ new IdentifierExpressionBackreference("indexVariable"),
+ BinaryOperatorType.LessThan,
+ new NamedNode("length", new PrimitiveExpression(PrimitiveExpression.AnyValue))
+ ),
+ Iterators = {
+ new ExpressionStatement(
+ new AssignmentExpression(
+ new IdentifierExpressionBackreference("indexVariable"),
+ new BinaryOperatorExpression(new IdentifierExpressionBackreference("indexVariable"), BinaryOperatorType.Add, new PrimitiveExpression(1))
+ ))
+ },
+ EmbeddedStatement = new BlockStatement {
+ Statements = {
+ new ExpressionStatement(new AssignmentExpression(
+ new NamedNode("itemVariable", new IdentifierExpression(Pattern.AnyString)),
+ new NamedNode("elementAccess", new AnyNode())
+ )),
+ new Repeat(new AnyNode("statements"))
+ }
+ }
+ };
+
+ ///
+ /// Reconstructs a foreach over an inline array from the for loop the compiler
+ /// lowers it to: for (i = 0; i < N; i++) { item = <PrivateImplementationDetails>.InlineArrayElementRef(ref buffer, i); ... }.
+ /// The rewrite is only sound because the loop bound N equals the inline array length,
+ /// which proves the index is always in range: InlineArrayElementRef is the compiler's
+ /// unchecked element accessor, whereas the C# inline-array indexer buffer[i] is
+ /// bounds-checked, so the two only agree when the index is provably in-bounds. A loop that
+ /// does not match this exact shape keeps the (unnameable but faithful) helper call.
+ ///
+ Statement? TransformForeachOnInlineArray(ForStatement forStatement)
+ {
+ if (!context.Settings.ForEachStatement || !context.Settings.InlineArrays)
+ return null;
+ Match m = forOnInlineArrayPattern.Match(forStatement);
+ if (!m.Success)
+ return null;
+ var itemVariable = m.Get("itemVariable").Single().GetILVariable();
+ var indexVariable = m.Get("indexVariable").Single().GetILVariable();
+ if (itemVariable == null || indexVariable == null)
+ return null;
+
+ // The loop body must start with `item = InlineArrayElementRef(ref buffer, index)`.
+ if (m.Get("elementAccess").Single() is not InvocationExpression elementAccess)
+ return null;
+ if (elementAccess.GetSymbol() is not IMethod { DeclaringType.FullName: "" } helper)
+ return null;
+ if (helper.Name is not ("InlineArrayElementRef" or "InlineArrayElementRefReadOnly"))
+ return null;
+ if (elementAccess.Arguments.Count != 2)
+ return null;
+ // arg0: `ref buffer`, arg1: the loop index.
+ if (elementAccess.Arguments.First() is not DirectionExpression { Expression: IdentifierExpression bufferIdentifier })
+ return null;
+ var bufferVariable = bufferIdentifier.GetILVariable();
+ if (bufferVariable == null)
+ return null;
+ if (elementAccess.Arguments.Last() is not IdentifierExpression indexIdentifier
+ || indexIdentifier.GetILVariable() != indexVariable)
+ return null;
+
+ // Soundness: the loop counts 0..length-1 over exactly the inline array's length, so the
+ // index is provably in range. Any other bound (or a non-inline-array buffer) is rejected.
+ if (bufferVariable.Type.GetInlineArrayLength() is not int arrayLength)
+ return null;
+ if (m.Get("length").Single().Value is not int loopBound || loopBound != arrayLength)
+ return null;
+
+ if (!VariableCanBeUsedAsForeachLocal(itemVariable, forStatement))
+ return null;
+ // The index is a pure counter: stored at init + increment, loaded at the condition,
+ // the increment, and the element access; never captured by address.
+ if (indexVariable.StoreCount != 2 || indexVariable.LoadCount != 3 || indexVariable.AddressCount != 0)
+ return null;
+
+ context.Step("Introduce foreach over inline array", forStatement);
+ // Take the buffer reference for the `in` expression before dropping the element access.
+ var inExpression = bufferIdentifier.Detach();
+ // Reuse the loop body (preserving its annotations) after removing its leading
+ // `item = .InlineArrayElementRef(ref buffer, i)` statement.
+ var body = (BlockStatement)forStatement.EmbeddedStatement;
+ body.Statements.First().Remove();
+ var foreachStmt = new ForeachStatement {
+ VariableType = context.Settings.AnonymousTypes && itemVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVariable.Type),
+ VariableDesignation = new SingleVariableDesignation { Identifier = itemVariable.Name! },
+ InExpression = inExpression,
+ EmbeddedStatement = body.Detach()
+ };
+ foreachStmt.CopyAnnotationsFrom(forStatement);
+ itemVariable.Kind = IL.VariableKind.ForeachLocal;
+ foreachStmt.VariableDesignation.AddAnnotation(new ILVariableResolveResult(itemVariable, itemVariable.Type));
+ forStatement.ReplaceWith(foreachStmt);
+ context.EndStep(foreachStmt);
+ return foreachStmt;
+ }
+
static readonly ForStatement forOnArrayMultiDimPattern = new ForStatement {
Initializers = { },
Condition = new BinaryOperatorExpression(