diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs index 12d9ae141..f0d06f5dd 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs @@ -556,6 +556,25 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator } builder.AppendLine("\t}"); builder.AppendLine(); + + // Single-pass enumeration of children in slot order (O(children) total) for AstNode.Children + // and the visitors' VisitChildren, instead of the per-step O(slots) index scan (NextSibling). + // Collection slots iterate via their own enumerator, which tolerates removing/replacing the + // current child during traversal. + builder.AppendLine("\tinternal override System.Collections.Generic.IEnumerable GetChildNodes()"); + builder.AppendLine("\t{"); + builder.AppendLine("\t\tvar children = new System.Collections.Generic.List(GetChildCount());"); + foreach (var s in slots) + { + string field = FieldName(s.PropertyName); + if (s.IsCollection) + builder.AppendLine($"\t\tif ({field} != null) children.AddRange({field});"); + else + builder.AppendLine($"\t\tif ({field} != null) children.Add({field});"); + } + builder.AppendLine("\t\treturn children;"); + builder.AppendLine("\t}"); + builder.AppendLine(); } builder.AppendLine("}"); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs b/ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs index 06ba9b636..08b5b110b 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs @@ -213,19 +213,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } - public IEnumerable Children { - get { - AstNode? next; - for (AstNode? cur = FirstChild; cur != null; cur = next) - { - Debug.Assert(cur.parent == this); - // Remember next before yielding cur. - // This allows removing/replacing nodes while iterating. - next = cur.NextSibling; - yield return cur; - } - } - } + public IEnumerable Children => GetChildNodes(); + + // Enumerates the children in slot order in a single pass (O(children)). The generator overrides this + // per node; the base (a leaf node with no slots) has none. Collection slots iterate via their own + // enumerator, which tolerates removing/replacing the current child mid-traversal. + internal virtual IEnumerable GetChildNodes() => System.Linq.Enumerable.Empty(); /// /// Gets the ancestors of this node (excluding this node itself) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index a455ab48b..c1e293499 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -33,12 +33,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { protected virtual void VisitChildren(AstNode node) { - AstNode next; - for (var child = node.FirstChild; child != null; child = next) + // GetChildNodes enumerates in slot order and tolerates the visitor removing or replacing + // the current child. + foreach (var child in node.GetChildNodes()) { - // Store next to allow the loop to continue - // if the visitor removes/replaces child. - next = child.NextSibling; child.AcceptVisitor(this); } } @@ -707,12 +705,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { protected virtual T VisitChildren(AstNode node) { - AstNode next; - for (var child = node.FirstChild; child != null; child = next) + // GetChildNodes enumerates in slot order and tolerates the visitor removing or replacing + // the current child. + foreach (var child in node.GetChildNodes()) { - // Store next to allow the loop to continue - // if the visitor removes/replaces child. - next = child.NextSibling; child.AcceptVisitor(this); } return default(T); @@ -1382,12 +1378,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { protected virtual S VisitChildren(AstNode node, T data) { - AstNode next; - for (var child = node.FirstChild; child != null; child = next) + // GetChildNodes enumerates in slot order and tolerates the visitor removing or replacing + // the current child. + foreach (var child in node.GetChildNodes()) { - // Store next to allow the loop to continue - // if the visitor removes/replaces child. - next = child.NextSibling; child.AcceptVisitor(this, data); } return default(S);