diff --git a/Directory.Packages.props b/Directory.Packages.props
index 51879bb08..55b538fc4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,8 +15,10 @@
+
+
diff --git a/ICSharpCode.Decompiler.Generators.Attributes/DecompilerAstNodeAttribute.cs b/ICSharpCode.Decompiler.Generators.Attributes/DecompilerAstNodeAttribute.cs
new file mode 100644
index 000000000..b0d1924dc
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators.Attributes/DecompilerAstNodeAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace ICSharpCode.Decompiler.CSharp.Syntax
+{
+ public sealed class DecompilerAstNodeAttribute : Attribute
+ {
+ public DecompilerAstNodeAttribute(bool hasNullNode) { }
+ }
+
+ public sealed class ExcludeFromMatchAttribute : Attribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Generators.Attributes/ICSharpCode.Decompiler.Generators.Attributes.csproj b/ICSharpCode.Decompiler.Generators.Attributes/ICSharpCode.Decompiler.Generators.Attributes.csproj
new file mode 100644
index 000000000..dbdcea46b
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators.Attributes/ICSharpCode.Decompiler.Generators.Attributes.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
new file mode 100644
index 000000000..c11027f53
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
@@ -0,0 +1,289 @@
+using System.Collections;
+using System.Collections.Immutable;
+using System.Text;
+
+using ICSharpCode.Decompiler.CSharp.Syntax;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace ICSharpCode.Decompiler.Generators;
+
+[Generator]
+internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
+{
+ record AstNodeAdditions(string NodeName, bool NeedsAcceptImpls, bool NeedsVisitor, bool NeedsNullNode, int NullNodeBaseCtorParamCount, bool IsTypeNode, string VisitMethodName, string VisitMethodParamType, EquatableArray<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? MembersToMatch);
+
+ AstNodeAdditions GetAstNodeAdditions(GeneratorAttributeSyntaxContext context, CancellationToken ct)
+ {
+ var targetSymbol = (INamedTypeSymbol)context.TargetSymbol;
+ var attribute = context.Attributes.SingleOrDefault(ad => ad.AttributeClass?.Name == "DecompilerAstNodeAttribute")!;
+ var (visitMethodName, paramTypeName) = targetSymbol.Name switch {
+ "ErrorExpression" => ("ErrorNode", "AstNode"),
+ string s when s.Contains("AstType") => (s.Replace("AstType", "Type"), s),
+ _ => (targetSymbol.Name, targetSymbol.Name),
+ };
+
+ List<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? membersToMatch = null;
+
+ if (!targetSymbol.MemberNames.Contains("DoMatch"))
+ {
+ membersToMatch = new();
+
+ var astNodeType = (INamedTypeSymbol)context.SemanticModel.GetSpeculativeSymbolInfo(context.TargetNode.Span.Start, SyntaxFactory.ParseTypeName("AstNode"), SpeculativeBindingOption.BindAsTypeOrNamespace).Symbol!;
+
+ if (targetSymbol.BaseType!.MemberNames.Contains("MatchAttributesAndModifiers"))
+ membersToMatch.Add(("MatchAttributesAndModifiers", null!, false, false));
+
+ foreach (var m in targetSymbol.GetMembers())
+ {
+ if (m is not IPropertySymbol property || property.IsIndexer || property.IsOverride)
+ continue;
+ if (property.GetAttributes().Any(a => a.AttributeClass?.Name == nameof(ExcludeFromMatchAttribute)))
+ continue;
+ if (property.Type.MetadataName is "CSharpTokenNode" or "TextLocation")
+ continue;
+ switch (property.Type)
+ {
+ case INamedTypeSymbol named when named.IsDerivedFrom(astNodeType) || named.MetadataName == "AstNodeCollection`1":
+ membersToMatch.Add((property.Name, named.Name, true, false));
+ break;
+ case INamedTypeSymbol { TypeKind: TypeKind.Enum } named when named.GetMembers().Any(_ => _.Name == "Any"):
+ membersToMatch.Add((property.Name, named.Name, false, true));
+ break;
+ default:
+ membersToMatch.Add((property.Name, property.Type.Name, false, false));
+ break;
+ }
+ }
+ }
+
+ return new(targetSymbol.Name, !targetSymbol.MemberNames.Contains("AcceptVisitor"),
+ NeedsVisitor: !targetSymbol.IsAbstract && targetSymbol.BaseType!.IsAbstract,
+ NeedsNullNode: (bool)attribute.ConstructorArguments[0].Value!,
+ NullNodeBaseCtorParamCount: targetSymbol.InstanceConstructors.Min(m => m.Parameters.Length),
+ IsTypeNode: targetSymbol.Name == "AstType" || targetSymbol.BaseType?.Name == "AstType",
+ visitMethodName, paramTypeName, membersToMatch?.ToEquatableArray());
+ }
+
+ void WriteGeneratedMembers(SourceProductionContext context, AstNodeAdditions source)
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
+ builder.AppendLine();
+
+ builder.AppendLine("#nullable enable");
+ builder.AppendLine();
+
+ builder.AppendLine($"partial class {source.NodeName}");
+ builder.AppendLine("{");
+
+ if (source.NeedsNullNode)
+ {
+ bool needsNew = source.NodeName != "AstNode";
+
+ builder.AppendLine($" {(needsNew ? "new " : "")}public static readonly {source.NodeName} Null = new Null{source.NodeName}();");
+
+ builder.AppendLine($@"
+ sealed class Null{source.NodeName} : {source.NodeName}
+ {{
+ public override NodeType NodeType => NodeType.Unknown;
+
+ public override bool IsNull => true;
+
+ public override void AcceptVisitor(IAstVisitor visitor)
+ {{
+ visitor.VisitNullNode(this);
+ }}
+
+ public override T AcceptVisitor(IAstVisitor visitor)
+ {{
+ return visitor.VisitNullNode(this);
+ }}
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {{
+ return visitor.VisitNullNode(this, data);
+ }}
+
+ protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
+ {{
+ return other == null || other.IsNull;
+ }}");
+
+ if (source.IsTypeNode)
+ {
+ builder.AppendLine(
+ $@"
+
+ public override Decompiler.TypeSystem.ITypeReference ToTypeReference(Resolver.NameLookupMode lookupMode, Decompiler.TypeSystem.InterningProvider? interningProvider = null)
+ {{
+ return Decompiler.TypeSystem.SpecialType.UnknownType;
+ }}"
+ );
+ }
+
+ if (source.NullNodeBaseCtorParamCount > 0)
+ {
+ builder.AppendLine($@"
+
+ public Null{source.NodeName}() : base({string.Join(", ", Enumerable.Repeat("default", source.NullNodeBaseCtorParamCount))}) {{ }}");
+ }
+
+ builder.AppendLine($@"
+ }}
+
+");
+
+ }
+
+ if (source.NeedsAcceptImpls && source.NeedsVisitor)
+ {
+ builder.Append($@" public override void AcceptVisitor(IAstVisitor visitor)
+ {{
+ visitor.Visit{source.NodeName}(this);
+ }}
+
+ public override T AcceptVisitor(IAstVisitor visitor)
+ {{
+ return visitor.Visit{source.NodeName}(this);
+ }}
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {{
+ return visitor.Visit{source.NodeName}(this, data);
+ }}
+");
+ }
+
+ if (source.MembersToMatch != null)
+ {
+ builder.Append($@" protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
+ {{
+ return other is {source.NodeName} o && !o.IsNull");
+
+ foreach (var (member, typeName, recursive, hasAny) in source.MembersToMatch)
+ {
+ if (member == "MatchAttributesAndModifiers")
+ {
+ builder.Append($"\r\n\t\t\t&& this.MatchAttributesAndModifiers(o, match)");
+ }
+ else if (recursive)
+ {
+ builder.Append($"\r\n\t\t\t&& this.{member}.DoMatch(o.{member}, match)");
+ }
+ else if (hasAny)
+ {
+ builder.Append($"\r\n\t\t\t&& (this.{member} == {typeName}.Any || this.{member} == o.{member})");
+ }
+ else
+ {
+ builder.Append($"\r\n\t\t\t&& this.{member} == o.{member}");
+ }
+ }
+
+ builder.Append(@";
+ }
+");
+ }
+
+ builder.AppendLine("}");
+
+ context.AddSource(source.NodeName + ".g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
+ }
+
+ private void WriteVisitors(SourceProductionContext context, ImmutableArray source)
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
+
+ source = source
+ .Concat([new("NullNode", false, true, false, 0, false, "NullNode", "AstNode", null), new("PatternPlaceholder", false, true, false, 0, false, "PatternPlaceholder", "AstNode", null)])
+ .ToImmutableArray();
+
+ WriteInterface("IAstVisitor", "void", "");
+ WriteInterface("IAstVisitor", "S", "");
+ WriteInterface("IAstVisitor", "S", ", T data");
+
+ context.AddSource("IAstVisitor.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
+
+ void WriteInterface(string name, string ret, string param)
+ {
+ builder.AppendLine($"public interface {name}");
+ builder.AppendLine("{");
+
+ foreach (var type in source.OrderBy(t => t.VisitMethodName))
+ {
+ if (!type.NeedsVisitor)
+ continue;
+
+ string extParams, paramName;
+ if (type.VisitMethodName == "PatternPlaceholder")
+ {
+ paramName = "placeholder";
+ extParams = ", PatternMatching.Pattern pattern" + param;
+ }
+ else
+ {
+ paramName = char.ToLowerInvariant(type.VisitMethodName[0]) + type.VisitMethodName.Substring(1);
+ extParams = param;
+ }
+
+ builder.AppendLine($"\t{ret} Visit{type.VisitMethodName}({type.VisitMethodParamType} {paramName}{extParams});");
+ }
+
+ builder.AppendLine("}");
+ }
+ }
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var astNodeAdditions = context.SyntaxProvider.ForAttributeWithMetadataName(
+ "ICSharpCode.Decompiler.CSharp.Syntax.DecompilerAstNodeAttribute",
+ (n, ct) => n is ClassDeclarationSyntax,
+ GetAstNodeAdditions);
+
+ var visitorMembers = astNodeAdditions.Collect();
+
+ context.RegisterSourceOutput(astNodeAdditions, WriteGeneratedMembers);
+ context.RegisterSourceOutput(visitorMembers, WriteVisitors);
+ }
+}
+
+readonly struct EquatableArray : IEquatable>, IEnumerable
+ where T : IEquatable
+{
+ readonly T[] array;
+
+ public EquatableArray(T[] array)
+ {
+ this.array = array ?? throw new ArgumentNullException(nameof(array));
+ }
+
+ public bool Equals(EquatableArray other)
+ {
+ return other.array.AsSpan().SequenceEqual(this.array);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)array).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return array.GetEnumerator();
+ }
+}
+
+static class EquatableArrayExtensions
+{
+ public static EquatableArray ToEquatableArray(this List array) where T : IEquatable
+ {
+ return new EquatableArray(array.ToArray());
+ }
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Generators/GeneratorAttributes.cs b/ICSharpCode.Decompiler.Generators/GeneratorAttributes.cs
new file mode 100644
index 000000000..b730ca53c
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators/GeneratorAttributes.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace ICSharpCode.Decompiler.CSharp.Syntax;
+
+public class DecompilerAstNodeAttribute(bool hasNullNode) : Attribute
+{
+ public bool HasNullNode { get; } = hasNullNode;
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Generators/ICSharpCode.Decompiler.Generators.csproj b/ICSharpCode.Decompiler.Generators/ICSharpCode.Decompiler.Generators.csproj
new file mode 100644
index 000000000..6bb2025e4
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators/ICSharpCode.Decompiler.Generators.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netstandard2.0
+ enable
+ enable
+ true
+ 12
+
+
+
+ 4.8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ICSharpCode.Decompiler.Generators/IsExternalInit.cs b/ICSharpCode.Decompiler.Generators/IsExternalInit.cs
new file mode 100644
index 000000000..7bac4c969
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators/IsExternalInit.cs
@@ -0,0 +1,3 @@
+namespace System.Runtime.CompilerServices;
+
+class IsExternalInit { }
diff --git a/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs b/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs
new file mode 100644
index 000000000..fa4747f76
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs
@@ -0,0 +1,32 @@
+using Microsoft.CodeAnalysis;
+
+namespace ICSharpCode.Decompiler.Generators;
+
+public static class RoslynHelpers
+{
+ public static IEnumerable GetTopLevelTypes(this IAssemblySymbol assembly)
+ {
+ foreach (var ns in TreeTraversal.PreOrder(assembly.GlobalNamespace, ns => ns.GetNamespaceMembers()))
+ {
+ foreach (var t in ns.GetTypeMembers())
+ {
+ yield return t;
+ }
+ }
+ }
+
+ public static bool IsDerivedFrom(this INamedTypeSymbol type, INamedTypeSymbol baseType)
+ {
+ INamedTypeSymbol? t = type;
+
+ while (t != null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(t, baseType))
+ return true;
+
+ t = t.BaseType;
+ }
+
+ return false;
+ }
+}
diff --git a/ICSharpCode.Decompiler.Generators/TreeTraversal.cs b/ICSharpCode.Decompiler.Generators/TreeTraversal.cs
new file mode 100644
index 000000000..4c194383a
--- /dev/null
+++ b/ICSharpCode.Decompiler.Generators/TreeTraversal.cs
@@ -0,0 +1,125 @@
+#nullable enable
+// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+namespace ICSharpCode.Decompiler.Generators;
+
+///
+/// Static helper methods for traversing trees.
+///
+internal static class TreeTraversal
+{
+ ///
+ /// Converts a tree data structure into a flat list by traversing it in pre-order.
+ ///
+ /// The root element of the tree.
+ /// The function that gets the children of an element.
+ /// Iterator that enumerates the tree structure in pre-order.
+ public static IEnumerable PreOrder(T root, Func?> recursion)
+ {
+ return PreOrder(new T[] { root }, recursion);
+ }
+
+ ///
+ /// Converts a tree data structure into a flat list by traversing it in pre-order.
+ ///
+ /// The root elements of the forest.
+ /// The function that gets the children of an element.
+ /// Iterator that enumerates the tree structure in pre-order.
+ public static IEnumerable PreOrder(IEnumerable input, Func?> recursion)
+ {
+ Stack> stack = new Stack>();
+ try
+ {
+ stack.Push(input.GetEnumerator());
+ while (stack.Count > 0)
+ {
+ while (stack.Peek().MoveNext())
+ {
+ T element = stack.Peek().Current;
+ yield return element;
+ IEnumerable? children = recursion(element);
+ if (children != null)
+ {
+ stack.Push(children.GetEnumerator());
+ }
+ }
+ stack.Pop().Dispose();
+ }
+ }
+ finally
+ {
+ while (stack.Count > 0)
+ {
+ stack.Pop().Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Converts a tree data structure into a flat list by traversing it in post-order.
+ ///
+ /// The root element of the tree.
+ /// The function that gets the children of an element.
+ /// Iterator that enumerates the tree structure in post-order.
+ public static IEnumerable PostOrder(T root, Func?> recursion)
+ {
+ return PostOrder(new T[] { root }, recursion);
+ }
+
+ ///
+ /// Converts a tree data structure into a flat list by traversing it in post-order.
+ ///
+ /// The root elements of the forest.
+ /// The function that gets the children of an element.
+ /// Iterator that enumerates the tree structure in post-order.
+ public static IEnumerable PostOrder(IEnumerable input, Func?> recursion)
+ {
+ Stack> stack = new Stack>();
+ try
+ {
+ stack.Push(input.GetEnumerator());
+ while (stack.Count > 0)
+ {
+ while (stack.Peek().MoveNext())
+ {
+ T element = stack.Peek().Current;
+ IEnumerable? children = recursion(element);
+ if (children != null)
+ {
+ stack.Push(children.GetEnumerator());
+ }
+ else
+ {
+ yield return element;
+ }
+ }
+ stack.Pop().Dispose();
+ if (stack.Count > 0)
+ yield return stack.Peek().Current;
+ }
+ }
+ finally
+ {
+ while (stack.Count > 0)
+ {
+ stack.Pop().Dispose();
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/packages.lock.json b/ICSharpCode.Decompiler/packages.lock.json
index e44c80edb..316fa0f4e 100644
--- a/ICSharpCode.Decompiler/packages.lock.json
+++ b/ICSharpCode.Decompiler/packages.lock.json
@@ -91,6 +91,9 @@
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ },
+ "icsharpcode.decompiler.generators.attributes": {
+ "type": "Project"
}
}
}
diff --git a/ILSpy.sln b/ILSpy.sln
index 099c49504..f6a5888a1 100644
--- a/ILSpy.sln
+++ b/ILSpy.sln
@@ -43,6 +43,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.Decompiler.Generators", "ICSharpCode.Decompiler.Generators\ICSharpCode.Decompiler.Generators.csproj", "{0792B524-622B-4B9D-8027-3531ED6A42EB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Decompiler.Generators.Attributes", "ICSharpCode.Decompiler.Generators.Attributes\ICSharpCode.Decompiler.Generators.Attributes.csproj", "{02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -101,6 +105,14 @@ Global
{81A30182-3378-4952-8880-F44822390040}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81A30182-3378-4952-8880-F44822390040}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81A30182-3378-4952-8880-F44822390040}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0792B524-622B-4B9D-8027-3531ED6A42EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0792B524-622B-4B9D-8027-3531ED6A42EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0792B524-622B-4B9D-8027-3531ED6A42EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0792B524-622B-4B9D-8027-3531ED6A42EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE