Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.14.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
<PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />
Expand Down
384 changes: 384 additions & 0 deletions ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,384 @@
using System.Collections;
using System.Collections.Immutable;
using System.Text;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace ICSharpCode.Decompiler.Generators;

[Generator]

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / build

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / build

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / build

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / build

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / Build (Release)

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / Build (Debug)

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / Build (Debug)

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 12 in ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

View workflow job for this annotation

GitHub Actions / Build (Release)

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)
internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
{
record AstNodeAdditions(string NodeName, bool NeedsAcceptImpls, bool NeedsVisitor, bool NeedsNullNode, bool NeedsPatternPlaceholder, 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 == "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!,
NeedsPatternPlaceholder: (bool)attribute.ConstructorArguments[1].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();

if (source.NeedsPatternPlaceholder)
{
builder.AppendLine("using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;");
}

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<T>(IAstVisitor<T> visitor)
{{
return visitor.VisitNullNode(this);
}}

public override S AcceptVisitor<T, S>(IAstVisitor<T, S> 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.NeedsPatternPlaceholder)
{
const string toTypeReferenceCode = @"

public override Decompiler.TypeSystem.ITypeReference ToTypeReference(Resolver.NameLookupMode lookupMode, Decompiler.TypeSystem.InterningProvider? interningProvider = null)
{
throw new System.NotSupportedException();
}";

builder.Append(
$@" public static implicit operator {source.NodeName}?(PatternMatching.Pattern? pattern)
{{
return pattern != null ? new PatternPlaceholder(pattern) : null;
}}

sealed class PatternPlaceholder : {source.NodeName}, INode
{{
readonly PatternMatching.Pattern child;

public PatternPlaceholder(PatternMatching.Pattern child)
{{
this.child = child;
}}

public override NodeType NodeType {{
get {{ return NodeType.Pattern; }}
}}

public override void AcceptVisitor(IAstVisitor visitor)
{{
visitor.VisitPatternPlaceholder(this, child);
}}

public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{{
return visitor.VisitPatternPlaceholder(this, child);
}}

public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{{
return visitor.VisitPatternPlaceholder(this, child, data);
}}

protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
{{
return child.DoMatch(other, match);
}}

bool PatternMatching.INode.DoMatchCollection(Role? role, PatternMatching.INode? pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo)
{{
return child.DoMatchCollection(role, pos, match, backtrackingInfo);
}}{(source.NodeName == "AstType" ? toTypeReferenceCode : "")}
}}
"
);
}

if (source.NeedsAcceptImpls && source.NeedsVisitor)
{
builder.Append($@" public override void AcceptVisitor(IAstVisitor visitor)
{{
visitor.Visit{source.VisitMethodName}(this);
}}

public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{{
return visitor.Visit{source.VisitMethodName}(this);
}}

public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{{
return visitor.Visit{source.VisitMethodName}(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 if (typeName == "String")
{
builder.Append($"\r\n\t\t\t&& MatchString(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));
}

void WriteVisitors(SourceProductionContext context, ImmutableArray<AstNodeAdditions> source)
{
var builder = new StringBuilder();

builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");

source = source
.Concat([new("NullNode", false, true, false, false, 0, false, "NullNode", "AstNode", null), new("PatternPlaceholder", false, true, false, false, 0, false, "PatternPlaceholder", "AstNode", null)])
.ToImmutableArray();

WriteInterface("IAstVisitor", "void", "");
WriteInterface("IAstVisitor<out S>", "S", "");
WriteInterface("IAstVisitor<in T, out S>", "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
.RegisterPostInitializationOutput(i => i.AddSource("DecompilerSyntaxTreeGeneratorAttributes.g.cs", @"

using System;

namespace Microsoft.CodeAnalysis
{
internal sealed partial class EmbeddedAttribute : global::System.Attribute
{
}
}

namespace ICSharpCode.Decompiler.CSharp.Syntax
{
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
sealed class DecompilerAstNodeAttribute : global::System.Attribute
{
public DecompilerAstNodeAttribute(bool hasNullNode = false, bool hasPatternPlaceholder = false) { }
}

[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
sealed class ExcludeFromMatchAttribute : global::System.Attribute
{
}
}

"));

context.RegisterSourceOutput(astNodeAdditions, WriteGeneratedMembers);
context.RegisterSourceOutput(visitorMembers, WriteVisitors);
}
}

readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
where T : IEquatable<T>
{
readonly T[] array;

public EquatableArray(T[] array)
{
this.array = array ?? throw new ArgumentNullException(nameof(array));
}

public bool Equals(EquatableArray<T> other)
{
return other.array.AsSpan().SequenceEqual(this.array);
}

public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)array).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return array.GetEnumerator();
}
}

static class EquatableArrayExtensions
{
public static EquatableArray<T> ToEquatableArray<T>(this List<T> array) where T : IEquatable<T>
{
return new EquatableArray<T>(array.ToArray());
}
}
Loading
Loading