Browse Source

Implement IncrementalGenerator

pull/7935/head
Steven He 5 years ago
parent
commit
18e7a11a02
  1. 11
      src/Avalonia.Base/Animation/Easings/Easing.cs
  2. 14
      src/Avalonia.SourceGenerator/IsExternalInit.cs
  3. 234
      src/Avalonia.SourceGenerator/SubtypesFactoryGenerator.cs

11
src/Avalonia.Base/Animation/Easings/Easing.cs

@ -37,14 +37,9 @@ namespace Avalonia.Animation.Easings
return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture));
}
if (TryCreateEasingInstance(e, out var easing))
{
return easing;
}
else
{
throw new FormatException($"Easing \"{e}\" was not found in {Namespace} namespace.");
}
return TryCreateEasingInstance(e, out var easing)
? easing
: throw new FormatException($"Easing \"{e}\" was not found in {Namespace} namespace.");
}
}
}

14
src/Avalonia.SourceGenerator/IsExternalInit.cs

@ -0,0 +1,14 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[ExcludeFromCodeCoverage, DebuggerNonUserCode]
internal static class IsExternalInit
{
}
}

234
src/Avalonia.SourceGenerator/SubtypesFactoryGenerator.cs

@ -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);
});
}
}
}

Loading…
Cancel
Save