diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index ccf2ddb5b..9c14435cb 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -168,17 +168,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static Issue3827Node RefLocalUsedAfterForLoop(Issue3827Node[] buckets, int hash, Issue3827Node newNode) { - // The ref local is used after the loop, so its declaration is hoisted in front of the - // for-statement; the for-initializer ref-assignment must stay on the declaration, because - // a ref local cannot be declared without an initializer. + // The ref local is used after the loop, so its declaration is hoisted in front of the loop. + // The loop is kept as a while-loop (rather than converted to a headless for-loop) so the + // ref-assignment stays on the declaration -- a ref local cannot be declared without an initializer. ref Issue3827Node reference = ref buckets[hash & 3]; - for (; reference != null; reference = ref reference.next) + while (reference != null) { if (reference.value == hash) { reference = newNode; break; } + reference = ref reference.next; } if (reference == null) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 9fc32bda2..8fbc4b9a3 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -588,39 +588,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms && identExpr.TypeArguments.Count == 0; } - /// - /// Matches the case where the variable's only initialization is the first initializer of a - /// for-statement (`for (v = expr; ...)`) and the declaration has to be placed in front of the - /// for-statement (e.g. because the variable is used after the loop). Used to keep a ref local's - /// initializer on its declaration (a ref local may not be declared without one). - /// - bool IsMatchingForInitializerAssignment(VariableToDeclare v, [NotNullWhen(true)] out ForStatement? forStatement, - [NotNullWhen(true)] out Statement? initializerStatement, [NotNullWhen(true)] out AssignmentExpression? assignment) - { - forStatement = null; - initializerStatement = null; - assignment = null; - - if (v.InsertionPoint.nextNode is not ForStatement { Parent: BlockStatement } fs) - return false; - - if (fs.Initializers.FirstOrDefault() is not ExpressionStatement { Expression: AssignmentExpression a } firstInit) - return false; - - if (a.Operator != AssignmentOperatorType.Assign - || a.Left is not IdentifierExpression identExpr - || identExpr.Identifier != v.Name - || identExpr.TypeArguments.Count != 0) - { - return false; - } - - forStatement = fs; - initializerStatement = firstInit; - assignment = a; - return true; - } - bool CombineDeclarationAndInitializer(VariableToDeclare v, TransformContext context) { if (v.Type.IsByRefLike) @@ -674,32 +641,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return vds; }, "Combine variable declaration with initializer")); } - else if (v.Type.IsByRefLike && IsMatchingForInitializerAssignment(v, out var forStatement, out var forInitializerStatement, out var forInitAssignment)) - { - // A by-ref-like local that is used after the loop has its declaration hoisted in - // front of the for-statement, but its only initialization is the for-initializer - // ref-assignment. A ref local must be declared with an initializer (CS8174), so move - // the ref-assignment's value up into the declaration and drop the for-initializer. - AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type); - if (v.ILVariable.IsRefReadOnly && type is ComposedType { HasRefSpecifier: true } composedType) - { - composedType.HasReadOnlySpecifier = true; - } - - var vds = new VariableDeclarationStatement(type, v.Name, forInitAssignment.Right.Detach()); - var init = vds.Variables.Single(); - init.AddAnnotation(forInitAssignment.Left.GetResolveResult()); - foreach (object annotation in forInitAssignment.Left.Annotations.Concat(forInitAssignment.Annotations)) - { - if (annotation is not ResolveResult) - { - init.AddAnnotation(annotation); - } - } - - forInitializerStatement.Remove(); - forStatement.Parent!.InsertChildBefore(forStatement, vds, Slots.Statement); - } else if (CanBeDeclaredAsOutVariable(v, out var dirExpr)) { // 'T v; SomeCall(out v);' can be combined to 'SomeCall(out T v);' diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index 1763677fd..a321808ff 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -203,6 +203,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (variable != m3.Get("ident").Single().GetILVariable()) return null; WhileStatement loop = (WhileStatement)next; + // Cannot convert to for loop, if the iteration variable is a ref local used after the loop: its + // declaration is hoisted in front, leaving a headless `for (; cond; v = ref ...)` whose only + // initialization is the for-initializer ref-assignment -- which can't be split from a ref local + // (CS8174). Keeping it a while-loop matches the source and keeps the initializer on the decl. + if (variable != null && variable.Type.IsByRefLike && IsVariableUsedAfter(loop, variable)) + return null; // Cannot convert to for loop, if any variable that is used in the "iterator" part of the pattern, // will be declared in the body of the while-loop. var iteratorStatement = m3.Get("iterator").Single(); @@ -247,6 +253,16 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return false; } + bool IsVariableUsedAfter(Statement loop, IL.ILVariable variable) + { + for (AstNode? sibling = loop.NextSibling; sibling != null; sibling = sibling.NextSibling) + { + if (sibling.DescendantsAndSelf.OfType().Any(ie => ie.GetILVariable() == variable)) + return true; + } + return false; + } + bool IteratorVariablesDeclaredInsideLoopBody(Statement iteratorStatement) { foreach (var id in iteratorStatement.DescendantsAndSelf.OfType())