Skip to content

Commit

Permalink
feat: add EnsureCapacity to dctionary foreach loops (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison authored Apr 20, 2023
1 parent 301105e commit 685d6ee
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public static class EnsureCapacityBuilder
x => x.ReturnType.SpecialType == SpecialType.System_Boolean && x.IsStatic && x.Parameters.Length == 2 && x.IsGenericMethod
);

// if non enumerated method doesnt exist then don't create EnsureCapacity
if (nonEnumeratedCountMethod == null)
return null;

// if source does not have a count use GetNonEnumeratedCount, calling EnusureCapacity if count is available
var typedNonEnumeratedCount = nonEnumeratedCountMethod.Construct(iEnumerable!.TypeArguments.ToArray());
return new EnsureCapacityNonEnumerated(targetSizeProperty, typedNonEnumeratedCount);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Diagnostics;
Expand Down Expand Up @@ -64,14 +65,17 @@ public static class DictionaryMappingBuilder
if (!ctx.Target.ImplementsGeneric(ctx.Types.IDictionaryT, out _))
return null;

var ensureCapacityStatement = EnsureCapacityBuilder.TryBuildEnsureCapacity(ctx.Source, ctx.Target, ctx.Types);

return new ForEachSetDictionaryMapping(
ctx.Source,
ctx.Target,
keyMapping,
valueMapping,
false,
objectFactory: objectFactory,
explicitCast: GetExplicitIndexer(ctx)
explicitCast: GetExplicitIndexer(ctx),
ensureCapacity: ensureCapacityStatement
);
}

Expand All @@ -91,12 +95,15 @@ public static class DictionaryMappingBuilder
}

// add values to dictionary by setting key values in a foreach loop
var ensureCapacityStatement = EnsureCapacityBuilder.TryBuildEnsureCapacity(ctx.Source, ctx.Target, ctx.Types);

return new ForEachSetDictionaryExistingTargetMapping(
ctx.Source,
ctx.Target,
keyMapping,
valueMapping,
explicitCast: GetExplicitIndexer(ctx)
explicitCast: GetExplicitIndexer(ctx),
ensureCapacity: ensureCapacityStatement
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;

Expand All @@ -19,19 +20,22 @@ public class ForEachSetDictionaryExistingTargetMapping : ExistingTargetMapping
private readonly ITypeMapping _keyMapping;
private readonly ITypeMapping _valueMapping;
private readonly INamedTypeSymbol? _explicitCast;
private readonly EnsureCapacity? _ensureCapacity;

public ForEachSetDictionaryExistingTargetMapping(
ITypeSymbol sourceType,
ITypeSymbol targetType,
ITypeMapping keyMapping,
ITypeMapping valueMapping,
INamedTypeSymbol? explicitCast
INamedTypeSymbol? explicitCast,
EnsureCapacity? ensureCapacity
)
: base(sourceType, targetType)
{
_keyMapping = keyMapping;
_valueMapping = valueMapping;
_explicitCast = explicitCast;
_ensureCapacity = ensureCapacity;
}

public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
Expand All @@ -47,6 +51,11 @@ public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx,
yield return LocalDeclarationStatement(DeclareVariable(castedVariable, cast));
}

if (_ensureCapacity != null)
{
yield return _ensureCapacity.Build(ctx, target);
}

var loopItemVariableName = ctx.NameBuilder.New(LoopItemVariableName);

var convertedKeyExpression = _keyMapping.Build(ctx.WithSource(MemberAccess(loopItemVariableName, KeyPropertyName)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Descriptors.ObjectFactories;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
Expand All @@ -26,9 +27,12 @@ public ForEachSetDictionaryMapping(
bool sourceHasCount,
ITypeSymbol? typeToInstantiate = null,
ObjectFactory? objectFactory = null,
INamedTypeSymbol? explicitCast = null
INamedTypeSymbol? explicitCast = null,
EnsureCapacity? ensureCapacity = null
)
: base(new ForEachSetDictionaryExistingTargetMapping(sourceType, targetType, keyMapping, valueMapping, explicitCast))
: base(
new ForEachSetDictionaryExistingTargetMapping(sourceType, targetType, keyMapping, valueMapping, explicitCast, ensureCapacity)
)
{
_sourceHasCount = sourceHasCount;
_objectFactory = objectFactory;
Expand Down
6 changes: 6 additions & 0 deletions src/Riok.Mapperly/Helpers/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ out bool isExplicit
return false;
}

if (t.IsAbstract)
{
isExplicit = false;
return true;
}

var interfaceSymbol = typedInterface.GetMembers(symbolName).First();

var symbolImplementaton = t.FindImplementationForInterfaceMember(interfaceSymbol);
Expand Down
67 changes: 67 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/DictionaryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ public void DictionaryToCustomDictionary()
.HaveSingleMethodBody(
"""
var target = new global::A();
target.EnsureCapacity(source.Count + target.Count);
foreach (var item in source)
{
target[item.Key] = item.Value;
Expand All @@ -217,6 +218,7 @@ public void DictionaryToCustomDictionaryWithObjectFactory()
.HaveSingleMethodBody(
"""
var target = CreateA();
target.EnsureCapacity(source.Count + target.Count);
foreach (var item in source)
{
target[item.Key] = item.Value;
Expand All @@ -227,6 +229,71 @@ public void DictionaryToCustomDictionaryWithObjectFactory()
);
}

[Fact]
public void MapToExistingDictionary()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"partial void Map(IDictionary<string, int> source, Dictionary<string, int> target);"
);
TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody(
"""
target.EnsureCapacity(source.Count + target.Count);
foreach (var item in source)
{
target[item.Key] = item.Value;
}
"""
);
}

[Fact]
public void MapToExistingCustomDictionary()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"partial void Map(IDictionary<string, int> source, A target);",
"class A : Dictionary<string, int> {}"
);
TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody(
"""
target.EnsureCapacity(source.Count + target.Count);
foreach (var item in source)
{
target[item.Key] = item.Value;
}
"""
);
}

[Fact]
public void KeyValueEnumerableToExistingDictionary()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"partial void Map(IEnumerable<KeyValuePair<string, int>> source, Dictionary<string, int> target);"
);
TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody(
"""
if (global::System.Linq.Enumerable.TryGetNonEnumeratedCount(source, out var sourceCount))
{
target.EnsureCapacity(sourceCount + target.Count);
}
foreach (var item in source)
{
target[item.Key] = item.Value;
}
"""
);
}

[Fact]
public void IDictionaryToExplicitDictionaryShouldCast()
{
Expand Down

0 comments on commit 685d6ee

Please sign in to comment.