Skip to content

Commit

Permalink
feat: improve incremental generator
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz committed Aug 30, 2023
1 parent 765aa0b commit 8e155be
Show file tree
Hide file tree
Showing 26 changed files with 556 additions and 240 deletions.
4 changes: 2 additions & 2 deletions src/Riok.Mapperly/Configuration/AttributeDataAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public TData AccessSingle<TAttribute, TData>(ISymbol symbol)
where TData : notnull => Access<TAttribute, TData>(symbol).FirstOrDefault();

public bool HasAttribute<TAttribute>(ISymbol symbol)
where TAttribute : Attribute => _symbolAccessor.GetAttributes<TAttribute>(symbol).Any();
where TAttribute : Attribute => _symbolAccessor.HasAttribute<TAttribute>(symbol);

public IEnumerable<TAttribute> Access<TAttribute>(ISymbol symbol)
where TAttribute : Attribute => Access<TAttribute, TAttribute>(symbol);
Expand All @@ -56,7 +56,7 @@ public IEnumerable<TData> Access<TAttribute, TData>(ISymbol symbol)
}
}

public static TData Access<TAttribute, TData>(AttributeData attrData)
internal static TData Access<TAttribute, TData>(AttributeData attrData)
where TAttribute : Attribute
where TData : notnull
{
Expand Down
24 changes: 13 additions & 11 deletions src/Riok.Mapperly/Configuration/MapperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,25 @@ namespace Riok.Mapperly.Configuration;
/// Newly added properties must also be added to <see cref="Riok.Mapperly.Helpers.MapperConfigurationBuilder"/>.
/// </remarks>
/// </summary>
public class MapperConfiguration
public record MapperConfiguration
{
public static readonly MapperConfiguration Default = new();

/// <summary>
/// Strategy on how to match mapping property names.
/// </summary>
public PropertyNameMappingStrategy? PropertyNameMappingStrategy { get; set; }
public PropertyNameMappingStrategy? PropertyNameMappingStrategy { get; init; }

/// <summary>
/// The default enum mapping strategy.
/// Can be overwritten on specific enums via mapping method configurations.
/// </summary>
public EnumMappingStrategy? EnumMappingStrategy { get; set; }
public EnumMappingStrategy? EnumMappingStrategy { get; init; }

/// <summary>
/// Whether the case should be ignored for enum mappings.
/// </summary>
public bool? EnumMappingIgnoreCase { get; set; }
public bool? EnumMappingIgnoreCase { get; init; }

/// <summary>
/// Specifies the behaviour in the case when the mapper tries to return <c>null</c> in a mapping method with a non-nullable return type.
Expand All @@ -35,15 +37,15 @@ public class MapperConfiguration
/// for value types <c>default</c>
/// and for reference types <c>new()</c> if a parameterless constructor exists or else an <see cref="ArgumentNullException"/> is thrown.
/// </summary>
public bool? ThrowOnMappingNullMismatch { get; set; }
public bool? ThrowOnMappingNullMismatch { get; init; }

/// <summary>
/// Specifies the behaviour in the case when the mapper tries to set a non-nullable property to a <c>null</c> value.
/// If set to <c>true</c> an <see cref="ArgumentNullException"/> is thrown.
/// If set to <c>false</c> the property assignment is ignored.
/// This is ignored for required init properties and <see cref="IQueryable{T}"/> projection mappings.
/// </summary>
public bool? ThrowOnPropertyMappingNullMismatch { get; set; }
public bool? ThrowOnPropertyMappingNullMismatch { get; init; }

/// <summary>
/// Specifies whether <c>null</c> values are assigned to the target.
Expand All @@ -52,15 +54,15 @@ public class MapperConfiguration
/// If <c>false</c>, <c>null</c> values are never assigned to the target property.
/// This is ignored for required init properties and <see cref="IQueryable{T}"/> projection mappings.
/// </summary>
public bool? AllowNullPropertyAssignment { get; set; }
public bool? AllowNullPropertyAssignment { get; init; }

/// <summary>
/// Whether to always deep copy objects.
/// Eg. when the type <c>Person[]</c> should be mapped to the same type <c>Person[]</c>,
/// when <c>false</c>, the same array is reused.
/// when <c>true</c>, the array and each person is cloned.
/// </summary>
public bool? UseDeepCloning { get; set; }
public bool? UseDeepCloning { get; init; }

/// <summary>
/// Enabled conversions which Mapperly automatically implements.
Expand All @@ -74,19 +76,19 @@ public class MapperConfiguration
/// <c>EnabledConversions = MappingConversionType.All &amp; ~MappingConversionType.ToStringMethod</c>
/// </example>
/// </summary>
public MappingConversionType? EnabledConversions { get; set; }
public MappingConversionType? EnabledConversions { get; init; }

/// <summary>
/// Enables the reference handling feature.
/// Disabled by default for performance reasons.
/// When enabled, an <see cref="IReferenceHandler"/> instance is passed through the mapping methods
/// to keep track of and reuse existing target object instances.
/// </summary>
public bool? UseReferenceHandling { get; set; }
public bool? UseReferenceHandling { get; init; }

/// <summary>
/// The ignore obsolete attribute strategy. Determines how <see cref="ObsoleteAttribute"/> marked members are mapped.
/// Defaults to <see cref="IgnoreObsoleteMembersStrategy.None"/>.
/// </summary>
public IgnoreObsoleteMembersStrategy? IgnoreObsoleteMembersStrategy { get; set; }
public IgnoreObsoleteMembersStrategy? IgnoreObsoleteMembersStrategy { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,49 @@
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Configuration;

namespace Riok.Mapperly.Helpers;
namespace Riok.Mapperly.Configuration;

public static class MapperConfigurationBuilder
public static class MapperConfigurationMerger
{
public static MapperAttribute Merge(MapperConfiguration mapperConfiguration, MapperConfiguration? defaultMapperConfiguration)
public static MapperAttribute Merge(MapperConfiguration mapperConfiguration, MapperConfiguration defaultMapperConfiguration)
{
var mapper = new MapperAttribute();
mapper.PropertyNameMappingStrategy =
mapperConfiguration.PropertyNameMappingStrategy
?? defaultMapperConfiguration?.PropertyNameMappingStrategy
?? defaultMapperConfiguration.PropertyNameMappingStrategy
?? mapper.PropertyNameMappingStrategy;

mapper.EnumMappingStrategy =
mapperConfiguration.EnumMappingStrategy ?? defaultMapperConfiguration?.EnumMappingStrategy ?? mapper.EnumMappingStrategy;
mapperConfiguration.EnumMappingStrategy ?? defaultMapperConfiguration.EnumMappingStrategy ?? mapper.EnumMappingStrategy;

mapper.EnumMappingIgnoreCase =
mapperConfiguration.EnumMappingIgnoreCase ?? defaultMapperConfiguration?.EnumMappingIgnoreCase ?? mapper.EnumMappingIgnoreCase;
mapperConfiguration.EnumMappingIgnoreCase ?? defaultMapperConfiguration.EnumMappingIgnoreCase ?? mapper.EnumMappingIgnoreCase;

mapper.ThrowOnMappingNullMismatch =
mapperConfiguration.ThrowOnMappingNullMismatch
?? defaultMapperConfiguration?.ThrowOnMappingNullMismatch
?? defaultMapperConfiguration.ThrowOnMappingNullMismatch
?? mapper.ThrowOnMappingNullMismatch;

mapper.ThrowOnPropertyMappingNullMismatch =
mapperConfiguration.ThrowOnPropertyMappingNullMismatch
?? defaultMapperConfiguration?.ThrowOnPropertyMappingNullMismatch
?? defaultMapperConfiguration.ThrowOnPropertyMappingNullMismatch
?? mapper.ThrowOnPropertyMappingNullMismatch;

mapper.AllowNullPropertyAssignment =
mapperConfiguration.AllowNullPropertyAssignment
?? defaultMapperConfiguration?.AllowNullPropertyAssignment
?? defaultMapperConfiguration.AllowNullPropertyAssignment
?? mapper.AllowNullPropertyAssignment;

mapper.UseDeepCloning = mapperConfiguration.UseDeepCloning ?? defaultMapperConfiguration?.UseDeepCloning ?? mapper.UseDeepCloning;
mapper.UseDeepCloning = mapperConfiguration.UseDeepCloning ?? defaultMapperConfiguration.UseDeepCloning ?? mapper.UseDeepCloning;

mapper.EnabledConversions =
mapperConfiguration.EnabledConversions ?? defaultMapperConfiguration?.EnabledConversions ?? mapper.EnabledConversions;
mapperConfiguration.EnabledConversions ?? defaultMapperConfiguration.EnabledConversions ?? mapper.EnabledConversions;

mapper.UseReferenceHandling =
mapperConfiguration.UseReferenceHandling ?? defaultMapperConfiguration?.UseReferenceHandling ?? mapper.UseReferenceHandling;
mapperConfiguration.UseReferenceHandling ?? defaultMapperConfiguration.UseReferenceHandling ?? mapper.UseReferenceHandling;

mapper.IgnoreObsoleteMembersStrategy =
mapperConfiguration.IgnoreObsoleteMembersStrategy
?? defaultMapperConfiguration?.IgnoreObsoleteMembersStrategy
?? defaultMapperConfiguration.IgnoreObsoleteMembersStrategy
?? mapper.IgnoreObsoleteMembersStrategy;

return mapper;
Expand Down
4 changes: 2 additions & 2 deletions src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public class MapperConfigurationReader
public MapperConfigurationReader(
AttributeDataAccessor dataAccessor,
ISymbol mapperSymbol,
MapperConfiguration? defaultMapperConfiguration
MapperConfiguration defaultMapperConfiguration
)
{
_dataAccessor = dataAccessor;
var mapperConfiguration = _dataAccessor.AccessSingle<MapperAttribute, MapperConfiguration>(mapperSymbol);
Mapper = MapperConfigurationBuilder.Merge(mapperConfiguration, defaultMapperConfiguration);
Mapper = MapperConfigurationMerger.Merge(mapperConfiguration, defaultMapperConfiguration);

_defaultConfiguration = new MappingConfiguration(
new EnumMappingConfiguration(
Expand Down
21 changes: 9 additions & 12 deletions src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Abstractions.ReferenceHandling;
using Riok.Mapperly.Configuration;
using Riok.Mapperly.Descriptors.ExternalMappings;
using Riok.Mapperly.Descriptors.MappingBodyBuilders;
using Riok.Mapperly.Descriptors.MappingBuilders;
using Riok.Mapperly.Descriptors.ObjectFactories;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors;

Expand All @@ -24,23 +24,20 @@ public class DescriptorBuilder
private ObjectFactoryCollection _objectFactories = ObjectFactoryCollection.Empty;

public DescriptorBuilder(
Compilation compilation,
ClassDeclarationSyntax mapperSyntax,
INamedTypeSymbol mapperSymbol,
WellKnownTypes wellKnownTypes,
CompilationContext compilationContext,
MapperDeclaration mapperDeclaration,
SymbolAccessor symbolAccessor,
MapperConfiguration? defaultMapperConfiguration
MapperConfiguration defaultMapperConfiguration
)
{
_mapperDescriptor = new MapperDescriptor(mapperSyntax, mapperSymbol, _methodNameBuilder);
_mapperDescriptor = new MapperDescriptor(mapperDeclaration, _methodNameBuilder);
_symbolAccessor = symbolAccessor;
_mappingBodyBuilder = new MappingBodyBuilder(_mappings);

var attributeAccessor = new AttributeDataAccessor(symbolAccessor);
_builderContext = new SimpleMappingBuilderContext(
compilation,
new MapperConfigurationReader(attributeAccessor, mapperSymbol, defaultMapperConfiguration),
wellKnownTypes,
compilationContext,
new MapperConfigurationReader(attributeAccessor, mapperDeclaration.Symbol, defaultMapperConfiguration),
_symbolAccessor,
attributeAccessor,
_mapperDescriptor,
Expand All @@ -50,13 +47,13 @@ public DescriptorBuilder(
);
}

public (MapperDescriptor descriptor, IReadOnlyCollection<Diagnostic> diagnostics) Build()
public (MapperDescriptor descriptor, IReadOnlyCollection<Diagnostic> diagnostics) Build(CancellationToken cancellationToken)
{
ReserveMethodNames();
ExtractObjectFactories();
ExtractUserMappings();
ExtractExternalMappings();
_mappingBodyBuilder.BuildMappingBodies();
_mappingBodyBuilder.BuildMappingBodies(cancellationToken);
BuildMappingMethodNames();
BuildReferenceHandlingParameters();
AddMappingsToDescriptor();
Expand Down
13 changes: 7 additions & 6 deletions src/Riok.Mapperly/Descriptors/MapperDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors;

public class MapperDescriptor
{
private readonly MapperDeclaration _declaration;
private readonly List<MethodMapping> _methodMappings = new();

public MapperDescriptor(ClassDeclarationSyntax syntax, INamedTypeSymbol symbol, UniqueNameBuilder nameBuilder)
public MapperDescriptor(MapperDeclaration declaration, UniqueNameBuilder nameBuilder)
{
Syntax = syntax;
Symbol = symbol;
_declaration = declaration;
NameBuilder = nameBuilder;
Name = BuildName(symbol);
Name = BuildName(declaration.Symbol);

if (!Symbol.ContainingNamespace.IsGlobalNamespace)
{
Expand All @@ -27,9 +28,9 @@ public MapperDescriptor(ClassDeclarationSyntax syntax, INamedTypeSymbol symbol,

public string? Namespace { get; }

public ClassDeclarationSyntax Syntax { get; }
public ClassDeclarationSyntax Syntax => _declaration.Syntax;

public INamedTypeSymbol Symbol { get; }
public INamedTypeSymbol Symbol => _declaration.Symbol;

public UniqueNameBuilder NameBuilder { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ public MappingBodyBuilder(MappingCollection mappings)
_mappings = mappings;
}

public void BuildMappingBodies()
public void BuildMappingBodies(CancellationToken cancellationToken)
{
foreach (var (typeMapping, ctx) in _mappings.DequeueMappingsToBuildBody())
{
cancellationToken.ThrowIfCancellationRequested();

switch (typeMapping)
{
case NewInstanceObjectMemberMethodMapping mapping:
Expand Down
25 changes: 17 additions & 8 deletions src/Riok.Mapperly/Descriptors/SimpleMappingBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Configuration;
using Riok.Mapperly.Descriptors.MappingBuilders;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors;

Expand All @@ -12,12 +13,12 @@ public class SimpleMappingBuilderContext
{
private readonly MapperDescriptor _descriptor;
private readonly List<Diagnostic> _diagnostics;
private readonly CompilationContext _compilationContext;
private readonly MapperConfigurationReader _configurationReader;

public SimpleMappingBuilderContext(
Compilation compilation,
CompilationContext compilationContext,
MapperConfigurationReader configurationReader,
WellKnownTypes types,
SymbolAccessor symbolAccessor,
AttributeDataAccessor attributeAccessor,
MapperDescriptor descriptor,
Expand All @@ -26,9 +27,8 @@ public SimpleMappingBuilderContext(
ExistingTargetMappingBuilder existingTargetMappingBuilder
)
{
Compilation = compilation;
Types = types;
SymbolAccessor = symbolAccessor;
_compilationContext = compilationContext;
_configurationReader = configurationReader;
_descriptor = descriptor;
_diagnostics = diagnostics;
Expand All @@ -39,9 +39,8 @@ ExistingTargetMappingBuilder existingTargetMappingBuilder

protected SimpleMappingBuilderContext(SimpleMappingBuilderContext ctx)
: this(
ctx.Compilation,
ctx._compilationContext,
ctx._configurationReader,
ctx.Types,
ctx.SymbolAccessor,
ctx.AttributeAccessor,
ctx._descriptor,
Expand All @@ -50,11 +49,11 @@ protected SimpleMappingBuilderContext(SimpleMappingBuilderContext ctx)
ctx.ExistingTargetMappingBuilder
) { }

public Compilation Compilation { get; }
public Compilation Compilation => _compilationContext.Compilation;

public MapperAttribute MapperConfiguration => _configurationReader.Mapper;

public WellKnownTypes Types { get; }
public WellKnownTypes Types => _compilationContext.Types;

public SymbolAccessor SymbolAccessor { get; }

Expand All @@ -69,6 +68,16 @@ public virtual bool IsConversionEnabled(MappingConversionType conversionType) =>

public void ReportDiagnostic(DiagnosticDescriptor descriptor, ISymbol? location, params object[] messageArgs)
{
// cannot use the symbol since it would break the incremental generator
// due to being different for each compilation.
for (var i = 0; i < messageArgs.Length; i++)
{
if (messageArgs[i] is ISymbol symbol)
{
messageArgs[i] = symbol.ToDisplayString();
}
}

var syntaxNode = location?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();
var nodeLocation = syntaxNode?.GetLocation();
_diagnostics.Add(Diagnostic.Create(descriptor, nodeLocation ?? _descriptor.Syntax.GetLocation(), messageArgs));
Expand Down
Loading

0 comments on commit 8e155be

Please sign in to comment.