9 changed files with 265 additions and 23 deletions
@ -0,0 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> |
|||
<Compile Include="$(MSBuildThisFileDirectory)/../src/Shared/SourceGeneratorAttributes.cs" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,17 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
</PackageReference> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" /> |
|||
<Compile Include="..\Shared\SourceGeneratorAttributes.cs" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,167 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|||
|
|||
namespace Avalonia.SourceGenerator |
|||
{ |
|||
internal class GenerateSubtypesSyntaxReceiver : ISyntaxReceiver |
|||
{ |
|||
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 static readonly string s_attributeName = typeof(SubtypesFactoryAttribute).FullName; |
|||
|
|||
public void Execute(GeneratorExecutionContext context) |
|||
{ |
|||
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); |
|||
} |
|||
|
|||
private bool IsSubtypeOf(ITypeSymbol type, ITypeSymbol baseType) |
|||
{ |
|||
if (type.BaseType is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (SymbolEqualityComparer.Default.Equals(type.BaseType, baseType)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return IsSubtypeOf(type.BaseType, baseType); |
|||
} |
|||
|
|||
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 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;
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace {method.ContainingNamespace} |
|||
{{ |
|||
{typeDecl} |
|||
{{ |
|||
{methodDeclText} |
|||
{{ |
|||
var hasMatch = false; |
|||
(hasMatch, {method.Parameters[1].Name}) = {method.Parameters[0].Name} switch |
|||
{{ |
|||
{string.Join("\n", candidateTypes.Select(i => $" \"{i.Name}\" => (true, ({method.Parameters[1].Type})new {i}()),"))} |
|||
_ => (false, default({method.Parameters[1].Type})) |
|||
}}; |
|||
|
|||
return hasMatch; |
|||
}} |
|||
}} |
|||
}}";
|
|||
|
|||
context.AddSource($"{type}.{method.MetadataName}.gen.cs", source); |
|||
} |
|||
} |
|||
|
|||
public void Initialize(GeneratorInitializationContext context) |
|||
{ |
|||
context.RegisterForSyntaxNotifications(() => _receiver); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.SourceGenerator |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] |
|||
internal sealed class SubtypesFactoryAttribute : Attribute |
|||
{ |
|||
public SubtypesFactoryAttribute(Type baseType, string @namespace) |
|||
{ |
|||
BaseType = baseType; |
|||
Namespace = @namespace; |
|||
} |
|||
|
|||
public string Namespace { get; } |
|||
public Type BaseType { get; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue