-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e4285b7
commit 5673cc2
Showing
136 changed files
with
911 additions
and
1,500 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
ICSharpCode.Decompiler.Generators.Attributes/DecompilerAstNodeAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
...Code.Decompiler.Generators.Attributes/ICSharpCode.Decompiler.Generators.Attributes.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
</Project> |
289 changes: 289 additions & 0 deletions
289
ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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.NeedsAcceptImpls && source.NeedsVisitor) | ||
{ | ||
builder.Append($@" public override void AcceptVisitor(IAstVisitor visitor) | ||
{{ | ||
visitor.Visit{source.NodeName}(this); | ||
}} | ||
public override T AcceptVisitor<T>(IAstVisitor<T> visitor) | ||
{{ | ||
return visitor.Visit{source.NodeName}(this); | ||
}} | ||
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> 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<AstNodeAdditions> 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<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.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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System; | ||
|
||
namespace ICSharpCode.Decompiler.CSharp.Syntax; | ||
|
||
public class DecompilerAstNodeAttribute(bool hasNullNode) : Attribute | ||
{ | ||
public bool HasNullNode { get; } = hasNullNode; | ||
} |
25 changes: 25 additions & 0 deletions
25
ICSharpCode.Decompiler.Generators/ICSharpCode.Decompiler.Generators.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> | ||
<LangVersion>12</LangVersion> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup> | ||
<RoslynVersion>4.8.0</RoslynVersion> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\ICSharpCode.Decompiler.Generators.Attributes\ICSharpCode.Decompiler.Generators.Attributes.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Common" VersionOverride="$(RoslynVersion)" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" VersionOverride="$(RoslynVersion)" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" VersionOverride="$(RoslynVersion)" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace System.Runtime.CompilerServices; | ||
|
||
class IsExternalInit { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace ICSharpCode.Decompiler.Generators; | ||
|
||
public static class RoslynHelpers | ||
{ | ||
public static IEnumerable<INamedTypeSymbol> 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; | ||
} | ||
} |
Oops, something went wrong.