Skip to content

Commit

Permalink
chore: add member, mappable member and attribute caching
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Jul 10, 2023
1 parent e2f1e4e commit 8bce46c
Show file tree
Hide file tree
Showing 22 changed files with 239 additions and 119 deletions.
9 changes: 4 additions & 5 deletions src/Riok.Mapperly/Configuration/AttributeDataAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ namespace Riok.Mapperly.Configuration;
internal class AttributeDataAccessor
{
private readonly WellKnownTypes _types;
private readonly SymbolAccessor _symbolAccessor;

public AttributeDataAccessor(WellKnownTypes types)
public AttributeDataAccessor(WellKnownTypes types, SymbolAccessor symbolAccessor)
{
_types = types;
_symbolAccessor = symbolAccessor;
}

public T AccessSingle<T>(ISymbol symbol)
Expand Down Expand Up @@ -42,11 +44,8 @@ public IEnumerable<TData> Access<TAttribute, TData>(ISymbol symbol)
{
var attrType = typeof(TAttribute);
var dataType = typeof(TData);
var attrSymbol = _types.Get($"{attrType.Namespace}.{attrType.Name}");

var attrDatas = symbol
.GetAttributes()
.Where(x => SymbolEqualityComparer.Default.Equals(x.AttributeClass?.ConstructedFrom ?? x.AttributeClass, attrSymbol));
var attrDatas = _symbolAccessor.GetAttributes<TAttribute>(symbol);

foreach (var attrData in attrDatas)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Riok.Mapperly/Configuration/MapperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ public class MapperConfiguration
private readonly MappingConfiguration _defaultConfiguration;
private readonly AttributeDataAccessor _dataAccessor;

public MapperConfiguration(WellKnownTypes wellKnownTypes, ISymbol mapperSymbol)
public MapperConfiguration(WellKnownTypes wellKnownTypes, SymbolAccessor symbolAccessor, ISymbol mapperSymbol)
{
_dataAccessor = new AttributeDataAccessor(wellKnownTypes);
_dataAccessor = new AttributeDataAccessor(wellKnownTypes, symbolAccessor);
Mapper = _dataAccessor.AccessSingle<MapperAttribute>(mapperSymbol);
_defaultConfiguration = new MappingConfiguration(
new EnumMappingConfiguration(
Expand Down
10 changes: 7 additions & 3 deletions src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Riok.Mapperly.Descriptors;
public class DescriptorBuilder
{
private readonly MapperDescriptor _mapperDescriptor;
private readonly SymbolAccessor _symbolAccessor;

private readonly MappingCollection _mappings = new();
private readonly MethodNameBuilder _methodNameBuilder = new();
Expand All @@ -25,15 +26,18 @@ public DescriptorBuilder(
Compilation compilation,
ClassDeclarationSyntax mapperSyntax,
INamedTypeSymbol mapperSymbol,
WellKnownTypes wellKnownTypes
WellKnownTypes wellKnownTypes,
SymbolAccessor symbolAccessor
)
{
_mapperDescriptor = new MapperDescriptor(mapperSyntax, mapperSymbol, _methodNameBuilder);
_symbolAccessor = symbolAccessor;
_mappingBodyBuilder = new MappingBodyBuilder(_mappings);
_builderContext = new SimpleMappingBuilderContext(
compilation,
new MapperConfiguration(wellKnownTypes, mapperSymbol),
new MapperConfiguration(wellKnownTypes, symbolAccessor, mapperSymbol),
wellKnownTypes,
_symbolAccessor,
_mapperDescriptor,
sourceContext,
new MappingBuilder(_mappings),
Expand Down Expand Up @@ -77,7 +81,7 @@ private void ExtractUserMappings()

private void ReserveMethodNames()
{
foreach (var methodSymbol in _mapperDescriptor.Symbol.GetAllMembers())
foreach (var methodSymbol in _symbolAccessor.GetAllMembers(_mapperDescriptor.Symbol))
{
_methodNameBuilder.Reserve(methodSymbol.Name);
}
Expand Down
25 changes: 18 additions & 7 deletions src/Riok.Mapperly/Descriptors/Enumerables/CollectionInfoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ private readonly record struct CollectionTypeInfo(
new CollectionTypeInfo(CollectionType.ReadOnlyMemory, typeof(ReadOnlyMemory<>)),
};

public static CollectionInfos? Build(WellKnownTypes wellKnownTypes, ITypeSymbol source, ITypeSymbol target)
public static CollectionInfos? Build(
WellKnownTypes wellKnownTypes,
SymbolAccessor symbolAccessor,
ITypeSymbol source,
ITypeSymbol target
)
{
// check for enumerated type to quickly check that both are collection types
var enumeratedSourceType = GetEnumeratedType(wellKnownTypes, source);
Expand All @@ -74,13 +79,18 @@ private readonly record struct CollectionTypeInfo(
if (enumeratedTargetType == null)
return null;

var sourceInfo = BuildCollectionInfo(wellKnownTypes, source, enumeratedSourceType);
var targetInfo = BuildCollectionInfo(wellKnownTypes, target, enumeratedTargetType);
var sourceInfo = BuildCollectionInfo(wellKnownTypes, symbolAccessor, source, enumeratedSourceType);
var targetInfo = BuildCollectionInfo(wellKnownTypes, symbolAccessor, target, enumeratedTargetType);

return new CollectionInfos(sourceInfo, targetInfo);
}

private static CollectionInfo BuildCollectionInfo(WellKnownTypes wellKnownTypes, ITypeSymbol type, ITypeSymbol enumeratedType)
private static CollectionInfo BuildCollectionInfo(
WellKnownTypes wellKnownTypes,
SymbolAccessor symbolAccessor,
ITypeSymbol type,
ITypeSymbol enumeratedType
)
{
var collectionTypeInfo = GetCollectionTypeInfo(wellKnownTypes, type);
var typeInfo = collectionTypeInfo?.CollectionType ?? CollectionType.None;
Expand All @@ -90,7 +100,7 @@ private static CollectionInfo BuildCollectionInfo(WellKnownTypes wellKnownTypes,
typeInfo,
GetImplementedCollectionTypes(wellKnownTypes, type, typeInfo),
enumeratedType,
FindCountProperty(wellKnownTypes, type, typeInfo),
FindCountProperty(wellKnownTypes, symbolAccessor, type, typeInfo),
HasValidAddMethod(wellKnownTypes, type, typeInfo),
collectionTypeInfo?.Immutable == true
);
Expand Down Expand Up @@ -139,7 +149,7 @@ or CollectionType.SortedSet
|| t.HasImplicitGenericImplementation(types.Get(typeof(ISet<>)), nameof(ISet<object>.Add));
}

private static string? FindCountProperty(WellKnownTypes types, ITypeSymbol t, CollectionType typeInfo)
private static string? FindCountProperty(WellKnownTypes types, SymbolAccessor symbolAccessor, ITypeSymbol t, CollectionType typeInfo)
{
if (typeInfo is CollectionType.IEnumerable)
return null;
Expand All @@ -158,7 +168,8 @@ or CollectionType.ReadOnlyMemory
return "Count";

var intType = types.Get<int>();
var member = t.GetAccessibleMappableMembers()
var member = symbolAccessor
.GetAllAccessibleMappableMembers(t)
.FirstOrDefault(
x =>
x.Name is nameof(ICollection<object>.Count) or nameof(Array.Length)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Helpers;

namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;

Expand All @@ -19,8 +18,8 @@ public static class EnsureCapacityBuilder
if (ctx.CollectionInfos == null)
return null;

var capacityMethod = ctx.Target
.GetAllMethods(EnsureCapacityName)
var capacityMethod = ctx.SymbolAccessor
.GetAllMethods(ctx.Target, EnsureCapacityName)
.FirstOrDefault(x => x.Parameters.Length == 1 && x.Parameters[0].Type.SpecialType == SpecialType.System_Int32 && !x.IsStatic);

// if EnsureCapacity is not available then return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ private HashSet<string> InitIgnoredUnmatchedProperties(IEnumerable<string> allPr

private HashSet<string> GetSourceMemberNames()
{
return Mapping.SourceType.GetAccessibleMappableMembers().Select(x => x.Name).ToHashSet();
return BuilderContext.SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.SourceType).Select(x => x.Name).ToHashSet();
}

private Dictionary<string, IMappableMember> GetTargetMembers()
{
return Mapping.TargetType.GetAccessibleMappableMembers().ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
return BuilderContext.SymbolAccessor
.GetAllAccessibleMappableMembers(Mapping.TargetType)
.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
}

private Dictionary<string, List<PropertyMappingConfiguration>> GetMemberConfigurations()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ private static void BuildInitOnlyMemberMappings(INewInstanceBuilderContext<IMapp
ctx.Mapping.SourceType,
MemberPathCandidateBuilder.BuildMemberPathCandidates(targetMember.Name),
ctx.IgnoredSourceMemberNames,
ctx.BuilderContext.SymbolAccessor,
out var sourceMemberPath
)
)
Expand Down Expand Up @@ -97,7 +98,14 @@ IReadOnlyCollection<PropertyMappingConfiguration> memberConfigs
return;
}

if (!MemberPath.TryFind(ctx.Mapping.SourceType, memberConfig.Source.Path, out var sourceMemberPath))
if (
!MemberPath.TryFind(
ctx.Mapping.SourceType,
memberConfig.Source.Path,
ctx.BuilderContext.SymbolAccessor,
out var sourceMemberPath
)
)
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.SourceMemberNotFound,
Expand Down Expand Up @@ -182,15 +190,15 @@ private static void BuildConstructorMapping(INewInstanceBuilderContext<IMapping>
// ctors annotated with [Obsolete] are considered last unless they have a MapperConstructor attribute set
var ctorCandidates = namedTargetType.InstanceConstructors
.Where(ctor => ctor.IsAccessible())
.OrderByDescending(x => x.HasAttribute(ctx.BuilderContext.Types.Get<MapperConstructorAttribute>()))
.ThenBy(x => x.HasAttribute(ctx.BuilderContext.Types.Get<ObsoleteAttribute>()))
.OrderByDescending(x => ctx.BuilderContext.SymbolAccessor.HasAttribute<MapperConstructorAttribute>(x))
.ThenBy(x => ctx.BuilderContext.SymbolAccessor.HasAttribute<MapperConstructorAttribute>(x))
.ThenByDescending(x => x.Parameters.Length == 0)
.ThenByDescending(x => x.Parameters.Length);
foreach (var ctorCandidate in ctorCandidates)
{
if (!TryBuildConstructorMapping(ctx, ctorCandidate, out var mappedTargetMemberNames, out var constructorParameterMappings))
{
if (ctorCandidate.HasAttribute(ctx.BuilderContext.Types.Get<MapperConstructorAttribute>()))
if (ctx.BuilderContext.SymbolAccessor.HasAttribute<MapperConstructorAttribute>(ctorCandidate))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.CannotMapToConfiguredConstructor,
Expand Down Expand Up @@ -293,6 +301,7 @@ private static bool TryFindConstructorParameterSourcePath(
MemberPathCandidateBuilder.BuildMemberPathCandidates(parameter.Name),
ctx.IgnoredSourceMemberNames,
StringComparer.OrdinalIgnoreCase,
ctx.BuilderContext.SymbolAccessor,
out sourcePath
);
}
Expand All @@ -317,7 +326,7 @@ out sourcePath
return false;
}

if (!MemberPath.TryFind(ctx.Mapping.SourceType, memberConfig.Source.Path, out sourcePath))
if (!MemberPath.TryFind(ctx.Mapping.SourceType, memberConfig.Source.Path, ctx.BuilderContext.SymbolAccessor, out sourcePath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.SourceMemberNotFound,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public static void BuildMappingBody(IMembersContainerBuilderContext<IMemberAssig
MemberPathCandidateBuilder.BuildMemberPathCandidates(targetMember.Name),
ctx.IgnoredSourceMemberNames,
memberNameComparer,
ctx.BuilderContext.SymbolAccessor,
out var sourceMemberPath
)
)
Expand Down Expand Up @@ -75,7 +76,7 @@ private static void BuildMemberAssignmentMapping(
PropertyMappingConfiguration config
)
{
if (!MemberPath.TryFind(ctx.Mapping.TargetType, config.Target.Path, out var targetMemberPath))
if (!MemberPath.TryFind(ctx.Mapping.TargetType, config.Target.Path, ctx.BuilderContext.SymbolAccessor, out var targetMemberPath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.ConfiguredMappingTargetMemberNotFound,
Expand All @@ -85,7 +86,7 @@ PropertyMappingConfiguration config
return;
}

if (!MemberPath.TryFind(ctx.Mapping.SourceType, config.Source.Path, out var sourceMemberPath))
if (!MemberPath.TryFind(ctx.Mapping.SourceType, config.Source.Path, ctx.BuilderContext.SymbolAccessor, out var sourceMemberPath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.ConfiguredMappingSourceMemberNotFound,
Expand Down
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ bool clearDerivedTypes

public ITypeSymbol Target { get; }

public CollectionInfos? CollectionInfos => _collectionInfos ??= CollectionInfoBuilder.Build(Types, Source, Target);
public CollectionInfos? CollectionInfos => _collectionInfos ??= CollectionInfoBuilder.Build(Types, SymbolAccessor, Source, Target);

protected IMethodSymbol? UserSymbol { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ or CollectionType.IDictionary
or CollectionType.IReadOnlyDictionary
)
{
var sourceHasCount = ctx.Source
.GetAllProperties(CountPropertyName)
var sourceHasCount = ctx.SymbolAccessor
.GetAllProperties(ctx.Source, CountPropertyName)
.Any(x => !x.IsStatic && x is { IsIndexer: false, IsWriteOnly: false, Type.SpecialType: SpecialType.System_Int32 });

var targetDictionarySymbol = ctx.Types.Get(typeof(Dictionary<,>)).Construct(keyMapping.TargetType, valueMapping.TargetType);
Expand Down
23 changes: 10 additions & 13 deletions src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,15 @@ private static TypeMapping BuildEnumToEnumCastMapping(
{
var explicitMappingSourceNames = explicitMappings.Keys.Select(x => x.Name).ToHashSet();
var explicitMappingTargetNames = explicitMappings.Values.Select(x => x.Name).ToHashSet();
var sourceValues = ctx.Source
.GetMembers()
.OfType<IFieldSymbol>()
var sourceValues = ctx.SymbolAccessor
.GetAllFields(ctx.Source)
.Where(x => !explicitMappingSourceNames.Contains(x.Name))
.ToDictionary(field => field.Name, field => field.ConstantValue);
var targetValues = ctx.Target
.GetMembers()
.OfType<IFieldSymbol>()
var targetValues = ctx.SymbolAccessor
.GetAllFields(ctx.Target)
.Where(x => !explicitMappingTargetNames.Contains(x.Name))
.ToDictionary(field => field.Name, field => field.ConstantValue);
var targetMemberNames = ctx.Target.GetMembers().OfType<IFieldSymbol>().Select(x => x.Name).ToHashSet();
var targetMemberNames = ctx.SymbolAccessor.GetAllFields(ctx.Target).Select(x => x.Name).ToHashSet();

var missingTargetValues = targetValues.Where(
field =>
Expand All @@ -99,7 +97,7 @@ private static TypeMapping BuildEnumToEnumCastMapping(
var checkDefinedMode = checkTargetDefined switch
{
false => EnumCastMapping.CheckDefinedMode.NoCheck,
_ when ctx.Target.HasAttribute(ctx.Types.Get<FlagsAttribute>()) => EnumCastMapping.CheckDefinedMode.Flags,
_ when ctx.SymbolAccessor.HasAttribute<FlagsAttribute>(ctx.Target) => EnumCastMapping.CheckDefinedMode.Flags,
_ => EnumCastMapping.CheckDefinedMode.Value,
};

Expand All @@ -124,8 +122,8 @@ IReadOnlyDictionary<IFieldSymbol, IFieldSymbol> explicitMappings
)
{
var fallbackMapping = BuildFallbackMapping(ctx);
var targetFieldsByName = ctx.Target.GetMembers().OfType<IFieldSymbol>().ToDictionary(x => x.Name);
var sourceFieldsByName = ctx.Source.GetMembers().OfType<IFieldSymbol>().ToDictionary(x => x.Name);
var targetFieldsByName = ctx.SymbolAccessor.GetAllFields(ctx.Target).ToDictionary(x => x.Name);
var sourceFieldsByName = ctx.SymbolAccessor.GetAllFields(ctx.Source).ToDictionary(x => x.Name);

Func<IFieldSymbol, IFieldSymbol?> getTargetField;
if (ctx.Configuration.Enum.IgnoreCase)
Expand All @@ -143,9 +141,8 @@ IReadOnlyDictionary<IFieldSymbol, IFieldSymbol> explicitMappings
getTargetField = source => explicitMappings.GetValueOrDefault(source) ?? targetFieldsByName.GetValueOrDefault(source.Name);
}

var enumMemberMappings = ctx.Source
.GetMembers()
.OfType<IFieldSymbol>()
var enumMemberMappings = ctx.SymbolAccessor
.GetAllFields(ctx.Source)
.Select(x => (Source: x, Target: getTargetField(x)))
.Where(x => x.Target != null)
.ToDictionary(x => x.Source.Name, x => x.Target!.Name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public static class EnumToStringMappingBuilder

// to string => use an optimized method of Enum.ToString which would use slow reflection
// use Enum.ToString as fallback (for ex. for flags)
return new EnumToStringMapping(ctx.Source, ctx.Target, ctx.Source.GetMembers().OfType<IFieldSymbol>());
return new EnumToStringMapping(ctx.Source, ctx.Target, ctx.SymbolAccessor.GetAllFields(ctx.Source));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public static class ParseMappingBuilder

var targetIsNullable = ctx.Target.NonNullable(out var nonNullableTarget);

var parseMethodCandidates = nonNullableTarget
.GetAllMethods(ParseMethodName)
var parseMethodCandidates = ctx.SymbolAccessor
.GetAllMethods(nonNullableTarget, ParseMethodName)
.Where(
m =>
m.IsStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static class StringToEnumMappingBuilder
// however we currently don't support all features of Enum.Parse yet (ex. flags)
// therefore we use Enum.Parse as fallback.
var fallbackMapping = BuildFallbackParseMapping(ctx, genericEnumParseMethodSupported);
var members = ctx.Target.GetFields();
var members = ctx.SymbolAccessor.GetAllFields(ctx.Target);
if (fallbackMapping.FallbackMember != null)
{
// no need to explicitly map fallback value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static ObjectFactoryCollection ExtractObjectFactories(SimpleMappingBuilde
var objectFactories = mapperSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.HasAttribute(ctx.Types.Get<ObjectFactoryAttribute>()))
.Where(m => ctx.SymbolAccessor.HasAttribute<ObjectFactoryAttribute>(m))
.Select(x => BuildObjectFactory(ctx, x))
.WhereNotNull()
.ToList();
Expand Down
Loading

0 comments on commit 8bce46c

Please sign in to comment.