From a2c765e2bd171de2c4720516a52afd1cd34d106e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jun 2026 08:00:11 +0200 Subject: [PATCH] Generate DoMatch for the expression nodes Replace the hand-written DoMatch overrides on the expression nodes with generated ones. The generator matches every real child and structural member, so several matchers become stricter than the hand-written versions that under-matched (for example a previously-skipped child or an ignored flag), while Pretty output is unaffected. Convenience-string identifier slots are excluded so the name string is matched once rather than also via its token. Assisted-by: Claude:claude-opus-4-8:Claude Code --- .../DecompilerSyntaxTreeGenerator.cs | 13 +++++++++++- .../Expressions/IdentifierExpression.cs | 2 ++ .../Syntax/Expressions/IndexerExpression.cs | 6 ------ .../InterpolatedStringExpression.cs | 20 ------------------- .../Expressions/InvocationExpression.cs | 6 ------ .../CSharp/Syntax/Expressions/IsExpression.cs | 6 ------ .../Syntax/Expressions/LambdaExpression.cs | 6 ------ .../Expressions/MemberReferenceExpression.cs | 8 ++------ .../Expressions/NamedArgumentExpression.cs | 8 ++------ .../Syntax/Expressions/NamedExpression.cs | 10 +++------- 10 files changed, 21 insertions(+), 64 deletions(-) diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs index 2a3bc1b56..d75506deb 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs @@ -270,6 +270,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator {{ return visitor.Visit{source.VisitMethodName}(this, data); }} + "); } @@ -310,6 +311,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator builder.Append(@"; } + "); } @@ -338,6 +340,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator builder.AppendLine($"\t\tset => SetChildNode(ref {field}, value, {roleExpr});"); builder.AppendLine("\t}"); } + builder.AppendLine(); } // Flattened child-index space: slots in declaration order, a single slot occupying one index @@ -345,6 +348,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator builder.Append("\tinternal override int GetChildCount() => "); builder.Append(string.Join(" + ", slots.Select(s => s.IsCollection ? $"({FieldName(s.PropertyName)}?.Count ?? 0)" : "1"))); builder.AppendLine(";"); + builder.AppendLine(); builder.AppendLine("\tinternal override AstNode? GetChild(int index)"); builder.AppendLine("\t{"); @@ -363,6 +367,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator } builder.AppendLine("\t\tthrow new System.ArgumentOutOfRangeException(nameof(index));"); builder.AppendLine("\t}"); + builder.AppendLine(); builder.AppendLine("\tinternal override void SetChild(int index, AstNode? value)"); builder.AppendLine("\t{"); @@ -381,6 +386,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator } builder.AppendLine("\t\tthrow new System.ArgumentOutOfRangeException(nameof(index));"); builder.AppendLine("\t}"); + builder.AppendLine(); builder.AppendLine("\tinternal override Role GetChildSlot(int index)"); builder.AppendLine("\t{"); @@ -394,10 +400,12 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator } builder.AppendLine("\t\tthrow new System.ArgumentOutOfRangeException(nameof(index));"); builder.AppendLine("\t}"); + builder.AppendLine(); // One CSharpSlotInfo static per slot; node.Slot compares against these by object identity. foreach (var s in slots) builder.AppendLine($"\tpublic static readonly CSharpSlotInfo {s.PropertyName}Slot = new CSharpSlotInfo(\"{s.PropertyName}\", typeof({s.ElementType}), {(s.IsCollection ? "true" : "false")}, SlotKind.{s.KindName});"); + builder.AppendLine(); builder.AppendLine("\tinternal override CSharpSlotInfo GetChildSlotInfo(int index)"); builder.AppendLine("\t{"); @@ -411,6 +419,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator } builder.AppendLine("\t\tthrow new System.ArgumentOutOfRangeException(nameof(index));"); builder.AppendLine("\t}"); + builder.AppendLine(); if (slots.Any(s => s.IsCollection)) { @@ -420,6 +429,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator builder.AppendLine($"\t\tif (role == {s.RoleExpr}) return {s.PropertyName};"); builder.AppendLine("\t\treturn base.GetCollectionByRole(role);"); builder.AppendLine("\t}"); + builder.AppendLine(); } builder.AppendLine("\tinternal override void CloneChildrenInto(AstNode copyNode)"); @@ -436,11 +446,12 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator builder.AppendLine($"\t\tif ({field} != null) copy.{s.PropertyName} = ({s.PropertyType}){field}.Clone();"); } builder.AppendLine("\t}"); + builder.AppendLine(); } builder.AppendLine("}"); - context.AddSource(source.NodeName + ".g.cs", SourceText.From(builder.ToString(), Encoding.UTF8)); + context.AddSource(source.NodeName + ".g.cs", SourceText.From(builder.ToString().Replace("\r\n", "\n"), Encoding.UTF8)); } void WriteVisitors(SourceProductionContext context, ImmutableArray source) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs index 1f3cc1b81..e96fe04ee 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs @@ -55,6 +55,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + // The Identifier string is what DoMatch compares; the token slot is excluded to avoid matching it twice. + [ExcludeFromMatch] [Slot("Roles.Identifier")] public partial Identifier IdentifierToken { get; set; } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IndexerExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IndexerExpression.cs index a45e62055..ac87b60ae 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IndexerExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IndexerExpression.cs @@ -59,11 +59,5 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public IndexerExpression(Expression target, params Expression[] arguments) : this(target, (IEnumerable)arguments) { } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - IndexerExpression o = other as IndexerExpression; - return o != null && this.Target.DoMatch(o.Target, match) && this.Arguments.DoMatch(o.Arguments, match); - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs index 489fd12bd..5c0d3269e 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; - namespace ICSharpCode.Decompiler.CSharp.Syntax { /// @@ -25,12 +23,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { Content.AddRange(content); } - - protected internal override bool DoMatch(AstNode other, Match match) - { - InterpolatedStringExpression o = other as InterpolatedStringExpression; - return o != null && !o.IsNull && this.Content.DoMatch(o.Content, match); - } } [DecompilerAstNode(hasNullNode: true)] @@ -68,12 +60,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Alignment = alignment; Suffix = suffix; } - - protected internal override bool DoMatch(AstNode other, Match match) - { - Interpolation o = other as Interpolation; - return o != null && this.Expression.DoMatch(o.Expression, match); - } } /// @@ -93,11 +79,5 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { Text = text; } - - protected internal override bool DoMatch(AstNode other, Match match) - { - InterpolatedStringText o = other as InterpolatedStringText; - return o != null && o.Text == this.Text; - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InvocationExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InvocationExpression.cs index 2b13d60a7..d0b5bf5a9 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InvocationExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InvocationExpression.cs @@ -59,11 +59,5 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public InvocationExpression(Expression target, params Expression[] arguments) : this(target, (IEnumerable)arguments) { } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - InvocationExpression o = other as InvocationExpression; - return o != null && this.Target.DoMatch(o.Target, match) && this.Arguments.DoMatch(o.Arguments, match); - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IsExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IsExpression.cs index 3286d838b..511297b27 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IsExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IsExpression.cs @@ -50,12 +50,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax AddChild(expression, Roles.Expression); AddChild(type, Roles.Type); } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - IsExpression o = other as IsExpression; - return o != null && this.Expression.DoMatch(o.Expression, match) && this.Type.DoMatch(o.Type, match); - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs index 3b9efb943..c0ea00201 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs @@ -51,11 +51,5 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax [Slot("BodyRole")] public partial AstNode Body { get; set; } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - LambdaExpression o = other as LambdaExpression; - return o != null && this.IsAsync == o.IsAsync && this.Parameters.DoMatch(o.Parameters, match) && this.Body.DoMatch(o.Body, match); - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/MemberReferenceExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/MemberReferenceExpression.cs index 240c39baf..3b181a64b 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/MemberReferenceExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/MemberReferenceExpression.cs @@ -46,6 +46,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + // DoMatch compares the MemberName string; exclude the token slot to avoid matching it twice. + [ExcludeFromMatch] [Slot("Roles.Identifier")] public partial Identifier MemberNameToken { get; set; } @@ -72,11 +74,5 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public MemberReferenceExpression(Expression target, string memberName, params AstType[] arguments) : this(target, memberName, (IEnumerable)arguments) { } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - MemberReferenceExpression o = other as MemberReferenceExpression; - return o != null && this.Target.DoMatch(o.Target, match) && MatchString(this.MemberName, o.MemberName) && this.TypeArguments.DoMatch(o.TypeArguments, match); - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedArgumentExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedArgumentExpression.cs index 91f21210b..c51b74f99 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedArgumentExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedArgumentExpression.cs @@ -44,16 +44,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + // DoMatch compares the Name string; exclude the token slot to avoid matching it twice. + [ExcludeFromMatch] [Slot("Roles.Identifier")] public partial Identifier NameToken { get; set; } [Slot("Roles.Expression")] public partial Expression Expression { get; set; } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - NamedArgumentExpression o = other as NamedArgumentExpression; - return o != null && MatchString(this.Name, o.Name) && this.Expression.DoMatch(o.Expression, match); - } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedExpression.cs index d6ce03291..1f385fa50 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/NamedExpression.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { /// /// No standalone expression production: 'name = value' as used in object initializers (member_initializer), anonymous-object members (member_declarator), and named attribute arguments. - /// named_expression : identifier '=' expression ; + /// named_expression ::= identifier '=' expression /// [DecompilerAstNode(hasNullNode: false)] public partial class NamedExpression : Expression @@ -52,16 +52,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + // DoMatch compares the Name string; exclude the token slot to avoid matching it twice. + [ExcludeFromMatch] [Slot("Roles.Identifier")] public partial Identifier NameToken { get; set; } [Slot("Roles.Expression")] public partial Expression Expression { get; set; } - - protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) - { - var o = other as NamedExpression; - return o != null && MatchString(this.Name, o.Name) && this.Expression.DoMatch(o.Expression, match); - } } }