Skip to content

Commit

Permalink
Move code to use IIncrementalGenerator instead of ISourceGenerator (I…
Browse files Browse the repository at this point in the history
…mprove performance)
  • Loading branch information
rrmanzano committed Mar 13, 2023
1 parent b0889b3 commit 6d0243b
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
using Maui.BindableProperty.Generator.Core.BindableProperty.Implementation;
using Maui.BindableProperty.Generator.Core.BindableProperty.Implementation.Interfaces;
using Microsoft.CodeAnalysis.CSharp;
using System.Diagnostics;

namespace Maui.BindableProperty.Generator.Core.BindableProperty;


[Generator]
public class AutoBindablePropertyGenerator : ISourceGenerator
public class AutoBindablePropertyGenerator : IncrementalGeneratorBase, IIncrementalGenerator
{
private readonly List<Type> TypeImplementations = new() {
typeof(DefaultValue),
Expand Down Expand Up @@ -86,40 +86,11 @@ public AutoBindableAttribute(){}
}
}";

public void Execute(GeneratorExecutionContext context)
{
context.EachClass<AutoBindableSyntaxReceiver>(AutoBindableConstants.AttrClassDisplayString, (attributeSymbol, group) => {
var classNamedTypeSymbol = group.Key;
try
{
var classSource = this.ProcessClass(classNamedTypeSymbol, group.ToList(), attributeSymbol, context);
if (string.IsNullOrEmpty(classSource))
return;
context.AddSource($"{classNamedTypeSymbol.Name}.generated.cs", SourceText.From(classSource, Encoding.UTF8));
}
catch (Exception e)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
AutoBindableConstants.ExceptionMBPG001Id,
AutoBindableConstants.ExceptionTitle,
AutoBindableConstants.ExceptionMBPG001Message,
AutoBindableConstants.ProjectName,
DiagnosticSeverity.Error,
isEnabledByDefault: true
),
classNamedTypeSymbol.Locations.FirstOrDefault(),
classNamedTypeSymbol.ToDisplayString(),
e.ToString()
)
);
}
});
}

private string ProcessClass(INamedTypeSymbol classSymbol, List<IFieldSymbol> fields, ISymbol attributeSymbol, GeneratorExecutionContext context)
private string ProcessClass(
INamedTypeSymbol classSymbol,
List<IFieldSymbol> fields,
ISymbol attributeSymbol,
SourceProductionContext context)
{
if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default))
{
Expand Down Expand Up @@ -189,7 +160,7 @@ private void ProcessBindableProperty(
IFieldSymbol fieldSymbol,
ISymbol attributeSymbol,
INamedTypeSymbol classSymbol,
GeneratorExecutionContext context)
SourceProductionContext context)
{
// Get the name and type of the field
var fieldName = fieldSymbol.Name;
Expand Down Expand Up @@ -319,12 +290,40 @@ private string ChooseName(string fieldName, TypedConstant overridenNameOpt)
return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
}

public void Initialize(GeneratorInitializationContext context)
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Register the attribute source
context.RegisterForPostInitialization((i) => i.AddSource(AutoBindableConstants.AttrName, attributeText));
this.Initialize(
context,
SourceText.From(attributeText, Encoding.UTF8),
(attributeSymbol, group, spc) => {
var classNamedTypeSymbol = group.Key;
try
{
var classSource = this.ProcessClass(classNamedTypeSymbol, group.ToList(), attributeSymbol, spc);
if (string.IsNullOrEmpty(classSource))
return;
// Register a syntax receiver that will be created for each generation pass
context.RegisterForSyntaxNotifications(() => new AutoBindableSyntaxReceiver());
spc.AddSource($"{classNamedTypeSymbol.Name}.generated.cs", SourceText.From(classSource, Encoding.UTF8));
}
catch (Exception e)
{
spc.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
AutoBindableConstants.ExceptionMBPG001Id,
AutoBindableConstants.ExceptionTitle,
AutoBindableConstants.ExceptionMBPG001Message,
AutoBindableConstants.ProjectName,
DiagnosticSeverity.Error,
isEnabledByDefault: true
),
classNamedTypeSymbol.Locations.FirstOrDefault(),
classNamedTypeSymbol.ToDisplayString(),
e.ToString()
)
);
}
}
);
}
}

This file was deleted.

This file was deleted.

135 changes: 135 additions & 0 deletions src/Maui.BindableProperty.Generator/Core/IncrementalGeneratorBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Maui.BindableProperty.Generator.Core.BindableProperty;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Maui.BindableProperty.Generator.Core;

public abstract class IncrementalGeneratorBase
{
protected void Initialize(
IncrementalGeneratorInitializationContext context,
SourceText sourceText,
Action<INamedTypeSymbol, IGrouping<INamedTypeSymbol, IFieldSymbol>, SourceProductionContext> action
)
{
// Add the marker attribute
context.RegisterPostInitializationOutput((ctx) => ctx.AddSource(
AutoBindableConstants.AttrName,
sourceText));

// Do a simple filter for fields
var fieldsDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => IsSyntaxTargetForGeneration(s), // Select fields with attributes
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)) // Select the fields with the [AutoBindable] attribute
.Where(static m => m is not null)!; // Filter out attributed fields that we don't care about

// Combine the selected fields with the 'Compilation'
var compilationAndFields = context.CompilationProvider.Combine(fieldsDeclarations.Collect());

// Generate the source using the compilation and fields
context.RegisterSourceOutput(compilationAndFields,
(spc, source) =>
{
var compilation = source.Item1;
var fields = source.Item2;
if (fields.IsDefaultOrEmpty)
{
// Nothing to do yet
return;
}
var distinctFields = fields.Distinct();
// Convert each FieldDeclarationSyntax to an IFieldSymbol
var fieldsToGenerate = this.GetTypesToGenerate(compilation, distinctFields, spc.CancellationToken);
if (fieldsToGenerate.Count > 0)
{
// Generate the source code and add it to the output
var attributeSymbol = compilation.GetTypeByMetadataName(AutoBindableConstants.AttrClassDisplayString);
// Group the fields by class, and generate the source
#pragma warning disable RS1024 // Symbols should be compared for equality
foreach (var group in fieldsToGenerate.GroupBy(f => f.ContainingType) ?? Enumerable.Empty<IGrouping<INamedTypeSymbol, IFieldSymbol>>())
{
action?.Invoke(attributeSymbol, group, spc);
}
#pragma warning restore RS1024 // Symbols should be compared for equality
}
});
}

private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
=> node is FieldDeclarationSyntax m && m.AttributeLists.Count > 0;

private static FieldDeclarationSyntax GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
// We know the node is a ClassDeclarationSyntax thanks to IsSyntaxTargetForGeneration
var fieldsDeclarationSyntax = (FieldDeclarationSyntax)context.Node;

// Loop through all the attributes
foreach (VariableDeclaratorSyntax variable in fieldsDeclarationSyntax.Declaration.Variables)
{
// Get the symbol being declared by the field, and keep it if its annotated
if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol fieldSymbol)
{
// Weird, we couldn't get the symbol, ignore it
continue;
}

// Is the attribute the [AutoBindableAttribute] attribute?
if (fieldSymbol.GetAttributes().Any(ad => ad?.AttributeClass?.ToDisplayString() == AutoBindableConstants.AttrClassDisplayString))
{
// Return the class
return fieldsDeclarationSyntax;
}
}

// We didn't find the attribute we were looking for
return null;
}

private List<IFieldSymbol> GetTypesToGenerate(
Compilation compilation,
IEnumerable<FieldDeclarationSyntax> fields,
CancellationToken ct)
{
// Create a list to hold our output
var fieldsToGenerate = new List<IFieldSymbol>();

// Get the semantic representation of our marker attribute
var fieldAttribute = compilation.GetTypeByMetadataName(AutoBindableConstants.AttrClassDisplayString);

if (fieldAttribute == null)
{
// If this is null, the compilation couldn't find the marker attribute type
// which suggests there's something very wrong! Bail out..
return fieldsToGenerate;
}

foreach (FieldDeclarationSyntax fieldDeclarationSyntax in fields)
{
// Stop if we're asked to
ct.ThrowIfCancellationRequested();

// Get the semantic representation of the field syntax
foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables)
{
// Get the symbol being declared by the field, and keep it if its annotated
SemanticModel semanticModel = compilation.GetSemanticModel(fieldDeclarationSyntax.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol fieldSymbol)
{
// Something went wrong
continue;
}

fieldsToGenerate.Add(fieldSymbol);
}
}

return fieldsToGenerate;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@

<ItemGroup>
<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>

0 comments on commit 6d0243b

Please sign in to comment.