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 @@ -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<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("}");

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

@ -213,19 +213,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -213,19 +213,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
public IEnumerable<AstNode> 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<AstNode> 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<AstNode> GetChildNodes() => System.Linq.Enumerable.Empty<AstNode>();
/// <summary>
/// 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 @@ -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 @@ -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 @@ -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);

Loading…
Cancel
Save