|
|
|
@ -1,140 +1,48 @@ |
|
|
|
using System.Collections.Generic; |
|
|
|
using System; |
|
|
|
using System.Collections.Immutable; |
|
|
|
using System.Linq; |
|
|
|
using System.Threading; |
|
|
|
using Microsoft.CodeAnalysis; |
|
|
|
using Microsoft.CodeAnalysis.CSharp; |
|
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
|
|
|
|
|
|
namespace Avalonia.SourceGenerator |
|
|
|
{ |
|
|
|
internal class GenerateSubtypesSyntaxReceiver : ISyntaxReceiver |
|
|
|
[Generator(LanguageNames.CSharp)] |
|
|
|
public class SubtypesFactoryGenerator : IIncrementalGenerator |
|
|
|
{ |
|
|
|
public List<(MethodDeclarationSyntax, AttributeSyntax)> CandidateMethods { get; } = new(); |
|
|
|
public List<SyntaxNode> Types { get; } = new(); |
|
|
|
|
|
|
|
public void OnVisitSyntaxNode(SyntaxNode syntaxNode) |
|
|
|
{ |
|
|
|
if (syntaxNode is MethodDeclarationSyntax declarationSyntax) |
|
|
|
{ |
|
|
|
foreach (var attribute in declarationSyntax.AttributeLists.SelectMany(i => i.Attributes)) |
|
|
|
{ |
|
|
|
CandidateMethods.Add((declarationSyntax, attribute)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax) |
|
|
|
{ |
|
|
|
Types.Add(syntaxNode); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[Generator] |
|
|
|
internal class SubtypesFactoryGenerator : ISourceGenerator |
|
|
|
{ |
|
|
|
private readonly GenerateSubtypesSyntaxReceiver _receiver = new(); |
|
|
|
private record MethodTarget(IMethodSymbol Method, ITypeSymbol BaseType, string Namespace); |
|
|
|
private static readonly string s_attributeName = typeof(SubtypesFactoryAttribute).FullName; |
|
|
|
|
|
|
|
public void Execute(GeneratorExecutionContext context) |
|
|
|
private static bool IsSubtypeOf(ITypeSymbol type, ITypeSymbol baseType) |
|
|
|
{ |
|
|
|
var methods = new List<(IMethodSymbol, ITypeSymbol, string)>(); |
|
|
|
|
|
|
|
foreach (var (method, attribute) in _receiver.CandidateMethods) |
|
|
|
{ |
|
|
|
var semanticModel = context.Compilation.GetSemanticModel(method.SyntaxTree); |
|
|
|
var attributeTypeInfo = semanticModel.GetTypeInfo(attribute); |
|
|
|
if (attributeTypeInfo.Type is null || |
|
|
|
attributeTypeInfo.Type.ToString() != s_attributeName || |
|
|
|
attribute.ArgumentList is null) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var arguments = attribute.ArgumentList.Arguments; |
|
|
|
if (arguments.Count != 2) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpr || |
|
|
|
arguments[1].Expression is not LiteralExpressionSyntax and not IdentifierNameSyntax) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var type = semanticModel.GetTypeInfo(typeOfExpr.Type); |
|
|
|
var ns = semanticModel.GetConstantValue(arguments[1].Expression); |
|
|
|
var methodDeclInfo = semanticModel.GetDeclaredSymbol(method); |
|
|
|
|
|
|
|
if (type.Type is not ITypeSymbol baseType || |
|
|
|
ns.HasValue is false || |
|
|
|
ns.Value is not string nsValue || |
|
|
|
methodDeclInfo is not IMethodSymbol methodSymbol || |
|
|
|
methodSymbol.Parameters.Length != 2 || |
|
|
|
methodSymbol.Parameters[1].RefKind != RefKind.Out) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
methods.Add((methodSymbol, baseType, nsValue)); |
|
|
|
} |
|
|
|
|
|
|
|
var types = new List<ITypeSymbol>(); |
|
|
|
foreach (var type in _receiver.Types) |
|
|
|
{ |
|
|
|
var semanticModel = context.Compilation.GetSemanticModel(type.SyntaxTree); |
|
|
|
var decl = semanticModel.GetDeclaredSymbol(type); |
|
|
|
if (decl is ITypeSymbol typeSymbol) |
|
|
|
{ |
|
|
|
types.Add(typeSymbol); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
GenerateSubTypes(context, methods, types); |
|
|
|
return type.BaseType is not null && (SymbolEqualityComparer.Default.Equals(type.BaseType, baseType) || IsSubtypeOf(type.BaseType, baseType)); |
|
|
|
} |
|
|
|
|
|
|
|
private bool IsSubtypeOf(ITypeSymbol type, ITypeSymbol baseType) |
|
|
|
private static void GenerateSubTypes(SourceProductionContext context, MethodTarget methodTarget, ImmutableArray<ITypeSymbol> types) |
|
|
|
{ |
|
|
|
if (type.BaseType is null) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
var (method, baseType, @namespace) = methodTarget; |
|
|
|
var candidateTypes = types.Where(i => IsSubtypeOf(i, baseType)).Where(i => $"{i.ContainingNamespace}.".StartsWith($"{@namespace}.")).ToArray(); |
|
|
|
var type = method.ContainingType; |
|
|
|
var isGeneric = type.TypeParameters.Length > 0; |
|
|
|
var isClass = type.TypeKind == TypeKind.Class; |
|
|
|
|
|
|
|
if (SymbolEqualityComparer.Default.Equals(type.BaseType, baseType)) |
|
|
|
if (method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is not MethodDeclarationSyntax methodDecl) |
|
|
|
{ |
|
|
|
return true; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
return IsSubtypeOf(type.BaseType, baseType); |
|
|
|
} |
|
|
|
var parameters = new SeparatedSyntaxList<ParameterSyntax>().AddRange(methodDecl.ParameterList.Parameters.Select(i => i.WithAttributeLists(new SyntaxList<AttributeListSyntax>()))); |
|
|
|
|
|
|
|
private void GenerateSubTypes( |
|
|
|
GeneratorExecutionContext context, |
|
|
|
List<(IMethodSymbol Method, ITypeSymbol BaseType, string Namespace)> methods, |
|
|
|
List<ITypeSymbol> types) |
|
|
|
{ |
|
|
|
foreach (var (method, baseType, @namespace) in methods) |
|
|
|
{ |
|
|
|
var candidateTypes = types.Where(i => IsSubtypeOf(i, baseType)).Where(i => $"{i.ContainingNamespace}.".StartsWith($"{@namespace}.")).ToArray(); |
|
|
|
var type = method.ContainingType; |
|
|
|
var isGeneric = type.TypeParameters.Length > 0; |
|
|
|
var isClass = type.TypeKind == TypeKind.Class; |
|
|
|
|
|
|
|
if (method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is not MethodDeclarationSyntax methodDecl) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
var methodDeclText = methodDecl |
|
|
|
.WithAttributeLists(new SyntaxList<AttributeListSyntax>()) |
|
|
|
.WithParameterList(methodDecl.ParameterList.WithParameters(parameters)) |
|
|
|
.WithBody(null) |
|
|
|
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) |
|
|
|
.WithoutTrivia().ToString(); |
|
|
|
|
|
|
|
var parameters = new SeparatedSyntaxList<ParameterSyntax>().AddRange(methodDecl.ParameterList.Parameters.Select(i => i.WithAttributeLists(new SyntaxList<AttributeListSyntax>()))); |
|
|
|
|
|
|
|
var methodDeclText = methodDecl |
|
|
|
.WithAttributeLists(new SyntaxList<AttributeListSyntax>()) |
|
|
|
.WithParameterList(methodDecl.ParameterList.WithParameters(parameters)) |
|
|
|
.WithBody(null) |
|
|
|
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) |
|
|
|
.WithoutTrivia().ToString(); |
|
|
|
|
|
|
|
var typeDecl = $"partial {(isClass ? "class" : "struct")} {type.Name}{(isGeneric ? $"<{string.Join(", ", type.TypeParameters)}>" : "")}"; |
|
|
|
var source = $@"using System;
|
|
|
|
var typeDecl = $"partial {(isClass ? "class" : "struct")} {type.Name}{(isGeneric ? $"<{string.Join(", ", type.TypeParameters)}>" : "")}"; |
|
|
|
var source = $@"using System;
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
|
|
namespace {method.ContainingNamespace} |
|
|
|
@ -155,13 +63,99 @@ namespace {method.ContainingNamespace} |
|
|
|
}} |
|
|
|
}}";
|
|
|
|
|
|
|
|
context.AddSource($"{type}.{method.MetadataName}.gen.cs", source); |
|
|
|
context.AddSource($"{type}.{method.MetadataName}.gen.cs", source); |
|
|
|
} |
|
|
|
|
|
|
|
private static MethodTarget? PopulateMethodTargets(GeneratorSyntaxContext context, CancellationToken token) |
|
|
|
{ |
|
|
|
token.ThrowIfCancellationRequested(); |
|
|
|
if (context.Node is MethodDeclarationSyntax method) |
|
|
|
{ |
|
|
|
var attributes = method.AttributeLists.SelectMany(i => i.Attributes); |
|
|
|
var semanticModel = context.SemanticModel; |
|
|
|
foreach (var attribute in attributes) |
|
|
|
{ |
|
|
|
var attributeTypeInfo = semanticModel.GetTypeInfo(attribute); |
|
|
|
if (attributeTypeInfo.Type is null || |
|
|
|
attributeTypeInfo.Type.ToString() != s_attributeName || |
|
|
|
attribute.ArgumentList is null) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var arguments = attribute.ArgumentList.Arguments; |
|
|
|
if (arguments.Count != 2) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpr || |
|
|
|
arguments[1].Expression is not LiteralExpressionSyntax and not IdentifierNameSyntax) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var type = semanticModel.GetTypeInfo(typeOfExpr.Type); |
|
|
|
var ns = semanticModel.GetConstantValue(arguments[1].Expression); |
|
|
|
var methodDeclInfo = semanticModel.GetDeclaredSymbol(method); |
|
|
|
|
|
|
|
if (type.Type is not ITypeSymbol baseType || |
|
|
|
ns.HasValue is false || |
|
|
|
ns.Value is not string nsValue || |
|
|
|
methodDeclInfo is not IMethodSymbol methodSymbol || |
|
|
|
methodSymbol.Parameters.Length != 2 || |
|
|
|
methodSymbol.Parameters[1].RefKind != RefKind.Out) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
return new MethodTarget(methodSymbol, baseType, nsValue); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
public void Initialize(GeneratorInitializationContext context) |
|
|
|
public void Initialize(IncrementalGeneratorInitializationContext context) |
|
|
|
{ |
|
|
|
context.RegisterForSyntaxNotifications(() => _receiver); |
|
|
|
var typesProvider = context.SyntaxProvider.CreateSyntaxProvider( |
|
|
|
static (syntaxNode, token) => |
|
|
|
{ |
|
|
|
token.ThrowIfCancellationRequested(); |
|
|
|
return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax; |
|
|
|
}, |
|
|
|
static (syntaxContext, token) => |
|
|
|
{ |
|
|
|
token.ThrowIfCancellationRequested(); |
|
|
|
return syntaxContext.Node is ClassDeclarationSyntax or StructDeclarationSyntax && |
|
|
|
syntaxContext.SemanticModel.GetDeclaredSymbol(syntaxContext.Node) is ITypeSymbol typeSymbol |
|
|
|
? typeSymbol : null; |
|
|
|
}) |
|
|
|
.SelectMany((type, token) => |
|
|
|
{ |
|
|
|
token.ThrowIfCancellationRequested(); |
|
|
|
return type is null ? Array.Empty<ITypeSymbol>() : new ITypeSymbol[] { type }; |
|
|
|
}); |
|
|
|
|
|
|
|
var methodsProvider = context.SyntaxProvider.CreateSyntaxProvider( |
|
|
|
static (syntaxNode, token) => |
|
|
|
{ |
|
|
|
token.ThrowIfCancellationRequested(); |
|
|
|
return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax or MethodDeclarationSyntax { AttributeLists.Count: > 0 }; |
|
|
|
}, PopulateMethodTargets) |
|
|
|
.SelectMany((method, token) => |
|
|
|
{ |
|
|
|
token.ThrowIfCancellationRequested(); |
|
|
|
return method is null ? Array.Empty<MethodTarget>() : new MethodTarget[] { method }; |
|
|
|
}); |
|
|
|
|
|
|
|
var generateContext = methodsProvider.Combine(typesProvider.Collect()); |
|
|
|
|
|
|
|
context.RegisterSourceOutput(generateContext, static (sourceContext, source) => |
|
|
|
{ |
|
|
|
sourceContext.CancellationToken.ThrowIfCancellationRequested(); |
|
|
|
GenerateSubTypes(sourceContext, source.Left, source.Right); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|