Skip to content

Commit 5673cc2

Browse files
WIP
1 parent e4285b7 commit 5673cc2

File tree

136 files changed

+911
-1500
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+911
-1500
lines changed

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
1717
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
1818
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
19+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.12.0" />
1920
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
2021
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.12.0" />
22+
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0" />
2123
<PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
2224
<PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
2325
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace ICSharpCode.Decompiler.CSharp.Syntax
4+
{
5+
public sealed class DecompilerAstNodeAttribute : Attribute
6+
{
7+
public DecompilerAstNodeAttribute(bool hasNullNode) { }
8+
}
9+
10+
public sealed class ExcludeFromMatchAttribute : Attribute
11+
{
12+
}
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
</Project>
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
using System.Collections;
2+
using System.Collections.Immutable;
3+
using System.Text;
4+
5+
using ICSharpCode.Decompiler.CSharp.Syntax;
6+
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Text;
11+
12+
namespace ICSharpCode.Decompiler.Generators;
13+
14+
[Generator]
15+
internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
16+
{
17+
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);
18+
19+
AstNodeAdditions GetAstNodeAdditions(GeneratorAttributeSyntaxContext context, CancellationToken ct)
20+
{
21+
var targetSymbol = (INamedTypeSymbol)context.TargetSymbol;
22+
var attribute = context.Attributes.SingleOrDefault(ad => ad.AttributeClass?.Name == "DecompilerAstNodeAttribute")!;
23+
var (visitMethodName, paramTypeName) = targetSymbol.Name switch {
24+
"ErrorExpression" => ("ErrorNode", "AstNode"),
25+
string s when s.Contains("AstType") => (s.Replace("AstType", "Type"), s),
26+
_ => (targetSymbol.Name, targetSymbol.Name),
27+
};
28+
29+
List<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? membersToMatch = null;
30+
31+
if (!targetSymbol.MemberNames.Contains("DoMatch"))
32+
{
33+
membersToMatch = new();
34+
35+
var astNodeType = (INamedTypeSymbol)context.SemanticModel.GetSpeculativeSymbolInfo(context.TargetNode.Span.Start, SyntaxFactory.ParseTypeName("AstNode"), SpeculativeBindingOption.BindAsTypeOrNamespace).Symbol!;
36+
37+
if (targetSymbol.BaseType!.MemberNames.Contains("MatchAttributesAndModifiers"))
38+
membersToMatch.Add(("MatchAttributesAndModifiers", null!, false, false));
39+
40+
foreach (var m in targetSymbol.GetMembers())
41+
{
42+
if (m is not IPropertySymbol property || property.IsIndexer || property.IsOverride)
43+
continue;
44+
if (property.GetAttributes().Any(a => a.AttributeClass?.Name == nameof(ExcludeFromMatchAttribute)))
45+
continue;
46+
if (property.Type.MetadataName is "CSharpTokenNode" or "TextLocation")
47+
continue;
48+
switch (property.Type)
49+
{
50+
case INamedTypeSymbol named when named.IsDerivedFrom(astNodeType) || named.MetadataName == "AstNodeCollection`1":
51+
membersToMatch.Add((property.Name, named.Name, true, false));
52+
break;
53+
case INamedTypeSymbol { TypeKind: TypeKind.Enum } named when named.GetMembers().Any(_ => _.Name == "Any"):
54+
membersToMatch.Add((property.Name, named.Name, false, true));
55+
break;
56+
default:
57+
membersToMatch.Add((property.Name, property.Type.Name, false, false));
58+
break;
59+
}
60+
}
61+
}
62+
63+
return new(targetSymbol.Name, !targetSymbol.MemberNames.Contains("AcceptVisitor"),
64+
NeedsVisitor: !targetSymbol.IsAbstract && targetSymbol.BaseType!.IsAbstract,
65+
NeedsNullNode: (bool)attribute.ConstructorArguments[0].Value!,
66+
NullNodeBaseCtorParamCount: targetSymbol.InstanceConstructors.Min(m => m.Parameters.Length),
67+
IsTypeNode: targetSymbol.Name == "AstType" || targetSymbol.BaseType?.Name == "AstType",
68+
visitMethodName, paramTypeName, membersToMatch?.ToEquatableArray());
69+
}
70+
71+
void WriteGeneratedMembers(SourceProductionContext context, AstNodeAdditions source)
72+
{
73+
var builder = new StringBuilder();
74+
75+
builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
76+
builder.AppendLine();
77+
78+
builder.AppendLine("#nullable enable");
79+
builder.AppendLine();
80+
81+
builder.AppendLine($"partial class {source.NodeName}");
82+
builder.AppendLine("{");
83+
84+
if (source.NeedsNullNode)
85+
{
86+
bool needsNew = source.NodeName != "AstNode";
87+
88+
builder.AppendLine($" {(needsNew ? "new " : "")}public static readonly {source.NodeName} Null = new Null{source.NodeName}();");
89+
90+
builder.AppendLine($@"
91+
sealed class Null{source.NodeName} : {source.NodeName}
92+
{{
93+
public override NodeType NodeType => NodeType.Unknown;
94+
95+
public override bool IsNull => true;
96+
97+
public override void AcceptVisitor(IAstVisitor visitor)
98+
{{
99+
visitor.VisitNullNode(this);
100+
}}
101+
102+
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
103+
{{
104+
return visitor.VisitNullNode(this);
105+
}}
106+
107+
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
108+
{{
109+
return visitor.VisitNullNode(this, data);
110+
}}
111+
112+
protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
113+
{{
114+
return other == null || other.IsNull;
115+
}}");
116+
117+
if (source.IsTypeNode)
118+
{
119+
builder.AppendLine(
120+
$@"
121+
122+
public override Decompiler.TypeSystem.ITypeReference ToTypeReference(Resolver.NameLookupMode lookupMode, Decompiler.TypeSystem.InterningProvider? interningProvider = null)
123+
{{
124+
return Decompiler.TypeSystem.SpecialType.UnknownType;
125+
}}"
126+
);
127+
}
128+
129+
if (source.NullNodeBaseCtorParamCount > 0)
130+
{
131+
builder.AppendLine($@"
132+
133+
public Null{source.NodeName}() : base({string.Join(", ", Enumerable.Repeat("default", source.NullNodeBaseCtorParamCount))}) {{ }}");
134+
}
135+
136+
builder.AppendLine($@"
137+
}}
138+
139+
");
140+
141+
}
142+
143+
if (source.NeedsAcceptImpls && source.NeedsVisitor)
144+
{
145+
builder.Append($@" public override void AcceptVisitor(IAstVisitor visitor)
146+
{{
147+
visitor.Visit{source.NodeName}(this);
148+
}}
149+
150+
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
151+
{{
152+
return visitor.Visit{source.NodeName}(this);
153+
}}
154+
155+
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
156+
{{
157+
return visitor.Visit{source.NodeName}(this, data);
158+
}}
159+
");
160+
}
161+
162+
if (source.MembersToMatch != null)
163+
{
164+
builder.Append($@" protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
165+
{{
166+
return other is {source.NodeName} o && !o.IsNull");
167+
168+
foreach (var (member, typeName, recursive, hasAny) in source.MembersToMatch)
169+
{
170+
if (member == "MatchAttributesAndModifiers")
171+
{
172+
builder.Append($"\r\n\t\t\t&& this.MatchAttributesAndModifiers(o, match)");
173+
}
174+
else if (recursive)
175+
{
176+
builder.Append($"\r\n\t\t\t&& this.{member}.DoMatch(o.{member}, match)");
177+
}
178+
else if (hasAny)
179+
{
180+
builder.Append($"\r\n\t\t\t&& (this.{member} == {typeName}.Any || this.{member} == o.{member})");
181+
}
182+
else
183+
{
184+
builder.Append($"\r\n\t\t\t&& this.{member} == o.{member}");
185+
}
186+
}
187+
188+
builder.Append(@";
189+
}
190+
");
191+
}
192+
193+
builder.AppendLine("}");
194+
195+
context.AddSource(source.NodeName + ".g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
196+
}
197+
198+
private void WriteVisitors(SourceProductionContext context, ImmutableArray<AstNodeAdditions> source)
199+
{
200+
var builder = new StringBuilder();
201+
202+
builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
203+
204+
source = source
205+
.Concat([new("NullNode", false, true, false, 0, false, "NullNode", "AstNode", null), new("PatternPlaceholder", false, true, false, 0, false, "PatternPlaceholder", "AstNode", null)])
206+
.ToImmutableArray();
207+
208+
WriteInterface("IAstVisitor", "void", "");
209+
WriteInterface("IAstVisitor<out S>", "S", "");
210+
WriteInterface("IAstVisitor<in T, out S>", "S", ", T data");
211+
212+
context.AddSource("IAstVisitor.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
213+
214+
void WriteInterface(string name, string ret, string param)
215+
{
216+
builder.AppendLine($"public interface {name}");
217+
builder.AppendLine("{");
218+
219+
foreach (var type in source.OrderBy(t => t.VisitMethodName))
220+
{
221+
if (!type.NeedsVisitor)
222+
continue;
223+
224+
string extParams, paramName;
225+
if (type.VisitMethodName == "PatternPlaceholder")
226+
{
227+
paramName = "placeholder";
228+
extParams = ", PatternMatching.Pattern pattern" + param;
229+
}
230+
else
231+
{
232+
paramName = char.ToLowerInvariant(type.VisitMethodName[0]) + type.VisitMethodName.Substring(1);
233+
extParams = param;
234+
}
235+
236+
builder.AppendLine($"\t{ret} Visit{type.VisitMethodName}({type.VisitMethodParamType} {paramName}{extParams});");
237+
}
238+
239+
builder.AppendLine("}");
240+
}
241+
}
242+
243+
public void Initialize(IncrementalGeneratorInitializationContext context)
244+
{
245+
var astNodeAdditions = context.SyntaxProvider.ForAttributeWithMetadataName(
246+
"ICSharpCode.Decompiler.CSharp.Syntax.DecompilerAstNodeAttribute",
247+
(n, ct) => n is ClassDeclarationSyntax,
248+
GetAstNodeAdditions);
249+
250+
var visitorMembers = astNodeAdditions.Collect();
251+
252+
context.RegisterSourceOutput(astNodeAdditions, WriteGeneratedMembers);
253+
context.RegisterSourceOutput(visitorMembers, WriteVisitors);
254+
}
255+
}
256+
257+
readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
258+
where T : IEquatable<T>
259+
{
260+
readonly T[] array;
261+
262+
public EquatableArray(T[] array)
263+
{
264+
this.array = array ?? throw new ArgumentNullException(nameof(array));
265+
}
266+
267+
public bool Equals(EquatableArray<T> other)
268+
{
269+
return other.array.AsSpan().SequenceEqual(this.array);
270+
}
271+
272+
public IEnumerator<T> GetEnumerator()
273+
{
274+
return ((IEnumerable<T>)array).GetEnumerator();
275+
}
276+
277+
IEnumerator IEnumerable.GetEnumerator()
278+
{
279+
return array.GetEnumerator();
280+
}
281+
}
282+
283+
static class EquatableArrayExtensions
284+
{
285+
public static EquatableArray<T> ToEquatableArray<T>(this List<T> array) where T : IEquatable<T>
286+
{
287+
return new EquatableArray<T>(array.ToArray());
288+
}
289+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace ICSharpCode.Decompiler.CSharp.Syntax;
4+
5+
public class DecompilerAstNodeAttribute(bool hasNullNode) : Attribute
6+
{
7+
public bool HasNullNode { get; } = hasNullNode;
8+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
8+
<LangVersion>12</LangVersion>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<RoslynVersion>4.8.0</RoslynVersion>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\ICSharpCode.Decompiler.Generators.Attributes\ICSharpCode.Decompiler.Generators.Attributes.csproj" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="Microsoft.CodeAnalysis.Common" VersionOverride="$(RoslynVersion)" />
21+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" VersionOverride="$(RoslynVersion)" />
22+
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" VersionOverride="$(RoslynVersion)" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace System.Runtime.CompilerServices;
2+
3+
class IsExternalInit { }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace ICSharpCode.Decompiler.Generators;
4+
5+
public static class RoslynHelpers
6+
{
7+
public static IEnumerable<INamedTypeSymbol> GetTopLevelTypes(this IAssemblySymbol assembly)
8+
{
9+
foreach (var ns in TreeTraversal.PreOrder(assembly.GlobalNamespace, ns => ns.GetNamespaceMembers()))
10+
{
11+
foreach (var t in ns.GetTypeMembers())
12+
{
13+
yield return t;
14+
}
15+
}
16+
}
17+
18+
public static bool IsDerivedFrom(this INamedTypeSymbol type, INamedTypeSymbol baseType)
19+
{
20+
INamedTypeSymbol? t = type;
21+
22+
while (t != null)
23+
{
24+
if (SymbolEqualityComparer.Default.Equals(t, baseType))
25+
return true;
26+
27+
t = t.BaseType;
28+
}
29+
30+
return false;
31+
}
32+
}

0 commit comments

Comments
 (0)