Skip to content

Commit

Permalink
chore: refactor MapperGenerator to cache outputs.
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Jul 7, 2023
1 parent 4dc9788 commit 01ca6a6
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public class SourceGeneratorBenchmarks

private MSBuildWorkspace? _workspace;

private CSharpGeneratorDriver? _sampleDriver;
private GeneratorDriver? _sampleDriver;
private Compilation? _sampleCompilation;

private CSharpGeneratorDriver? _largeDriver;
private GeneratorDriver? _largeDriver;
private Compilation? _largeCompilation;

public SourceGeneratorBenchmarks()
Expand Down
8 changes: 4 additions & 4 deletions src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public class DescriptorBuilder
private readonly MethodNameBuilder _methodNameBuilder = new();
private readonly MappingBodyBuilder _mappingBodyBuilder;
private readonly SimpleMappingBuilderContext _builderContext;
private readonly List<Diagnostic> _diagnostics = new();

private ObjectFactoryCollection _objectFactories = ObjectFactoryCollection.Empty;

public DescriptorBuilder(
SourceProductionContext sourceContext,
Compilation compilation,
ClassDeclarationSyntax mapperSyntax,
INamedTypeSymbol mapperSymbol,
Expand All @@ -35,13 +35,13 @@ WellKnownTypes wellKnownTypes
new MapperConfiguration(wellKnownTypes, mapperSymbol),
wellKnownTypes,
_mapperDescriptor,
sourceContext,
_diagnostics,
new MappingBuilder(_mappings),
new ExistingTargetMappingBuilder(_mappings)
);
}

public MapperDescriptor Build()
public (MapperDescriptor descriptor, List<Diagnostic> errors) Build()
{
ReserveMethodNames();
ExtractObjectFactories();
Expand All @@ -50,7 +50,7 @@ public MapperDescriptor Build()
BuildMappingMethodNames();
BuildReferenceHandlingParameters();
AddMappingsToDescriptor();
return _mapperDescriptor;
return (_mapperDescriptor, _diagnostics);
}

private void ExtractObjectFactories()
Expand Down
22 changes: 10 additions & 12 deletions src/Riok.Mapperly/Descriptors/SimpleMappingBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ namespace Riok.Mapperly.Descriptors;
public class SimpleMappingBuilderContext
{
private readonly MapperDescriptor _descriptor;
private readonly SourceProductionContext _context;
private readonly List<Diagnostic> _diagnostics;
private readonly MapperConfiguration _configuration;

public SimpleMappingBuilderContext(
Compilation compilation,
MapperConfiguration configuration,
WellKnownTypes types,
MapperDescriptor descriptor,
SourceProductionContext context,
List<Diagnostic> diagnostics,
MappingBuilder mappingBuilder,
ExistingTargetMappingBuilder existingTargetMappingBuilder
)
Expand All @@ -28,7 +28,7 @@ ExistingTargetMappingBuilder existingTargetMappingBuilder
Types = types;
_configuration = configuration;
_descriptor = descriptor;
_context = context;
_diagnostics = diagnostics;
MappingBuilder = mappingBuilder;
ExistingTargetMappingBuilder = existingTargetMappingBuilder;
}
Expand All @@ -39,7 +39,7 @@ protected SimpleMappingBuilderContext(SimpleMappingBuilderContext ctx)
ctx._configuration,
ctx.Types,
ctx._descriptor,
ctx._context,
ctx._diagnostics,
ctx.MappingBuilder,
ctx.ExistingTargetMappingBuilder
) { }
Expand All @@ -57,14 +57,12 @@ protected SimpleMappingBuilderContext(SimpleMappingBuilderContext ctx)
public virtual bool IsConversionEnabled(MappingConversionType conversionType) =>
MapperConfiguration.EnabledConversions.HasFlag(conversionType);

public void ReportDiagnostic(DiagnosticDescriptor descriptor, ISymbol? location, params object[] messageArgs) =>
ReportDiagnostic(descriptor, location?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(), messageArgs);

public void ReportDiagnostic(DiagnosticDescriptor descriptor, SyntaxNode? location, params object[] messageArgs) =>
ReportDiagnostic(descriptor, location?.GetLocation(), messageArgs);
public void ReportDiagnostic(DiagnosticDescriptor descriptor, ISymbol? location, params object[] messageArgs)
{
var syntaxNode = location?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();
var nodeLocation = syntaxNode?.GetLocation();
_diagnostics.Add(Diagnostic.Create(descriptor, nodeLocation ?? _descriptor.Syntax.GetLocation(), messageArgs));
}

protected MappingConfiguration ReadConfiguration(IMethodSymbol? userSymbol) => _configuration.ForMethod(userSymbol);

private void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object[] messageArgs) =>
_context.ReportDiagnostic(Diagnostic.Create(descriptor, location ?? _descriptor.Syntax.GetLocation(), messageArgs));
}
5 changes: 1 addition & 4 deletions src/Riok.Mapperly/Emit/SourceEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ public static CompilationUnitSyntax Build(MapperDescriptor descriptor)
member = WrapInClassesAsNeeded(descriptor.Symbol, member);
member = WrapInNamespaceIfNeeded(descriptor.Namespace, member);

return CompilationUnit()
.WithMembers(SingletonList(member))
.WithLeadingTrivia(Comment("// <auto-generated />"), Nullable(true))
.NormalizeWhitespace();
return CompilationUnit().WithMembers(SingletonList(member)).WithLeadingTrivia(Comment("// <auto-generated />"), Nullable(true));
}

private static IEnumerable<MemberDeclarationSyntax> BuildMembers(MapperDescriptor descriptor, SourceEmitterContext sourceEmitterContext)
Expand Down
23 changes: 23 additions & 0 deletions src/Riok.Mapperly/Helpers/IncrementalValuesProviderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly.Helpers;
Expand All @@ -10,4 +11,26 @@ public static IncrementalValuesProvider<TSource> WhereNotNull<TSource>(this Incr
return source.Where(x => x != null);
#nullable enable
}

/// <summary>
/// Registers an output node into an <see cref="IncrementalGeneratorInitializationContext"/> to output diagnostics.
/// </summary>
/// <param name="context">The input <see cref="IncrementalGeneratorInitializationContext"/> instance.</param>
/// <param name="diagnostics">The input <see cref="IncrementalValuesProvider{TValues}"/> sequence of diagnostics.</param>
public static void ReportDiagnostics(
this IncrementalGeneratorInitializationContext context,
IncrementalValueProvider<ImmutableArray<Diagnostic>> diagnostics
)
{
context.RegisterSourceOutput(
diagnostics,
static (context, diagnostics) =>
{
foreach (var diagnostic in diagnostics)
{
context.ReportDiagnostic(diagnostic);
}
}
);
}
}
51 changes: 41 additions & 10 deletions src/Riok.Mapperly/MapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,70 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var mapperClassDeclarations = SyntaxProvider.GetClassDeclarations(context);

var compilationAndMappers = context.CompilationProvider.Combine(mapperClassDeclarations.Collect());
context.RegisterImplementationSourceOutput(compilationAndMappers, static (spc, source) => Execute(source.Left, source.Right, spc));
var mappersWithDiagnostics = compilationAndMappers.Select(
static (x, cancellationToken) => BuildDescriptors(x.Left, x.Right, cancellationToken)
);

// output the diagnostics
#if ROSLYN4_4_OR_GREATER
context.ReportDiagnostics(mappersWithDiagnostics.Select(static (source, _) => source.Diagnostics).WithTrackingName("diagnostics"));
#else
context.ReportDiagnostics(mappersWithDiagnostics.Select(static (source, _) => source.Diagnostics));
#endif

// split into mapper name pairs
var mappers = mappersWithDiagnostics.SelectMany(static (x, _) => x.Mappers);

context.RegisterImplementationSourceOutput(
mappers,
static (spc, source) =>
{
var mapperText = source.Body.NormalizeWhitespace().ToFullString();
spc.AddSource(source.FileName, SourceText.From(mapperText, Encoding.UTF8));
}
);
}

private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> mappers, SourceProductionContext ctx)
private static MapperResults BuildDescriptors(
Compilation compilation,
ImmutableArray<ClassDeclarationSyntax> mappers,
CancellationToken cancellationToken
)
{
if (mappers.IsDefaultOrEmpty)
return;
return MapperResults.Empty;

#if DEBUG_SOURCE_GENERATOR
DebuggerUtil.AttachDebugger();
#endif
var mapperAttributeSymbol = compilation.GetTypeByMetadataName(MapperAttributeName);
if (mapperAttributeSymbol == null)
return;
return MapperResults.Empty;

var wellKnownTypes = new WellKnownTypes(compilation);
var uniqueNameBuilder = new UniqueNameBuilder();

var diagnostics = new List<Diagnostic>();
var members = new List<MapperNode>();

foreach (var mapperSyntax in mappers.Distinct())
{
cancellationToken.ThrowIfCancellationRequested();
var mapperModel = compilation.GetSemanticModel(mapperSyntax.SyntaxTree);
if (mapperModel.GetDeclaredSymbol(mapperSyntax) is not INamedTypeSymbol mapperSymbol)
continue;

if (!mapperSymbol.HasAttribute(mapperAttributeSymbol))
continue;

var builder = new DescriptorBuilder(ctx, compilation, mapperSyntax, mapperSymbol, wellKnownTypes);
var descriptor = builder.Build();
var builder = new DescriptorBuilder(compilation, mapperSyntax, mapperSymbol, wellKnownTypes);
var (descriptor, descriptorDiagnostics) = builder.Build();

ctx.AddSource(
uniqueNameBuilder.New(descriptor.Name) + GeneratedFileSuffix,
SourceText.From(SourceEmitter.Build(descriptor).ToFullString(), Encoding.UTF8)
);
diagnostics.AddRange(descriptorDiagnostics);
members.Add(new MapperNode(SourceEmitter.Build(descriptor), uniqueNameBuilder.New(descriptor.Name) + GeneratedFileSuffix));
}

cancellationToken.ThrowIfCancellationRequested();
return new MapperResults(members.ToImmutableArray(), diagnostics.ToImmutableArray());
}
}
27 changes: 27 additions & 0 deletions src/Riok.Mapperly/MapperNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Riok.Mapperly;

public readonly struct MapperNode : IEquatable<MapperNode>
{
public MapperNode(CompilationUnitSyntax body, string fileName)
{
Body = body;
FileName = fileName;
}

public CompilationUnitSyntax Body { get; }
public string FileName { get; }

public bool Equals(MapperNode other) => Body.IsEquivalentTo(other.Body) && FileName == other.FileName;

public override bool Equals(object? obj) => obj is MapperNode other && Equals(other);

public override int GetHashCode()
{
unchecked
{
return (Body.GetHashCode() * 397) ^ FileName.GetHashCode();
}
}
}
50 changes: 50 additions & 0 deletions src/Riok.Mapperly/MapperResults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly;

public readonly struct MapperResults : IEquatable<MapperResults>
{
public static readonly MapperResults Empty = new(ImmutableArray<MapperNode>.Empty, ImmutableArray<Diagnostic>.Empty);

public MapperResults(ImmutableArray<MapperNode> mappers, ImmutableArray<Diagnostic> diagnostics)
{
Mappers = mappers;
Diagnostics = diagnostics;
}

public ImmutableArray<MapperNode> Mappers { get; }
public ImmutableArray<Diagnostic> Diagnostics { get; }

public bool Equals(MapperResults other)
{
return Mappers.SequenceEqual(other.Mappers) && Diagnostics.SequenceEqual(other.Diagnostics);
}

public override bool Equals(object? obj) => obj is MapperResults other && Equals(other);

public override int GetHashCode()
{
unchecked
{
var hash = 0;
foreach (var mapper in Mappers)
{
hash = Combine(hash, mapper.GetHashCode());
}
foreach (var diagnostic in Diagnostics)
{
hash = Combine(hash, diagnostic.GetHashCode());
}
return hash;
}

static int Combine(int h1, int h2)
{
// RyuJIT optimizes this to use the ROL instruction
// Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830
uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
return ((int)rol5 + h1) ^ h2;
}
}
}
Loading

0 comments on commit 01ca6a6

Please sign in to comment.