Browse Source

Generate a single-pass child enumerator (GetChildNodes)

AstNode.Children and the visitors' VisitChildren walked children via
FirstChild/NextSibling, an O(slots) index rescan per step. The generator now
emits GetChildNodes, which materializes the children in slot order in one pass
(O(children)); Children and VisitChildren iterate that. The snapshot makes the
walk tolerant of the visitor removing or replacing the current child (the
mutation pattern the old capture-next loop supported) without re-feeding the
loop.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3807/head
Siegfried Pammer 3 weeks ago committed by Siegfried Pammer
parent
commit
1a4a9dd41c
  1. 19
      ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
  2. 19
      ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs
  3. 24
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

19
ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

@ -556,6 +556,25 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
} }
builder.AppendLine("\t}"); builder.AppendLine("\t}");
builder.AppendLine(); 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<AstNode> GetChildNodes()");
builder.AppendLine("\t{");
builder.AppendLine("\t\tvar children = new System.Collections.Generic.List<AstNode>(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("}"); builder.AppendLine("}");

19
ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs

@ -213,19 +213,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
} }
public IEnumerable<AstNode> Children { public IEnumerable<AstNode> Children => GetChildNodes();
get {
AstNode? next; // Enumerates the children in slot order in a single pass (O(children)). The generator overrides this
for (AstNode? cur = FirstChild; cur != null; cur = next) // 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.
Debug.Assert(cur.parent == this); internal virtual IEnumerable<AstNode> GetChildNodes() => System.Linq.Enumerable.Empty<AstNode>();
// Remember next before yielding cur.
// This allows removing/replacing nodes while iterating.
next = cur.NextSibling;
yield return cur;
}
}
}
/// <summary> /// <summary>
/// Gets the ancestors of this node (excluding this node itself) /// Gets the ancestors of this node (excluding this node itself)

24
ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

@ -33,12 +33,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
protected virtual void VisitChildren(AstNode node) protected virtual void VisitChildren(AstNode node)
{ {
AstNode next; // GetChildNodes enumerates in slot order and tolerates the visitor removing or replacing
for (var child = node.FirstChild; child != null; child = next) // 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); child.AcceptVisitor(this);
} }
} }
@ -707,12 +705,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
protected virtual T VisitChildren(AstNode node) protected virtual T VisitChildren(AstNode node)
{ {
AstNode next; // GetChildNodes enumerates in slot order and tolerates the visitor removing or replacing
for (var child = node.FirstChild; child != null; child = next) // 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); child.AcceptVisitor(this);
} }
return default(T); return default(T);
@ -1382,12 +1378,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
protected virtual S VisitChildren(AstNode node, T data) protected virtual S VisitChildren(AstNode node, T data)
{ {
AstNode next; // GetChildNodes enumerates in slot order and tolerates the visitor removing or replacing
for (var child = node.FirstChild; child != null; child = next) // 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); child.AcceptVisitor(this, data);
} }
return default(S); return default(S);

Loading…
Cancel
Save