Skip to content

Commit

Permalink
feat: add user implemented existing target mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Aug 1, 2023
1 parent e8fc876 commit 1cff8e3
Show file tree
Hide file tree
Showing 33 changed files with 367 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ private static IEnumerable<ITypeMapping> GetUserMappingCandidates(MappingBuilder
{
foreach (var userMapping in ctx.UserMappings)
{
// exclude runtime target type mappings
if (userMapping is UserDefinedNewInstanceRuntimeTargetTypeMapping)
// exclude runtime target type and user implemented existing type mappings
if (userMapping is UserDefinedNewInstanceRuntimeTargetTypeMapping or UserImplementedExistingTargetMethodMapping)
continue;

if (userMapping.CallableByOtherMappings)
Expand Down
9 changes: 9 additions & 0 deletions src/Riok.Mapperly/Descriptors/MappingCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ public void Add(ITypeMapping mapping)
_methodMappings.Add(methodMapping);
}

if (mapping.CallableByOtherMappings && mapping is UserImplementedExistingTargetMethodMapping existingMapping)
{
if (FindExistingInstanceMapping(mapping.SourceType, mapping.TargetType) is null)
{
_existingTargetMappings.Add(new TypeMappingKey(mapping), existingMapping);
}
return;
}

if (mapping.CallableByOtherMappings && Find(mapping.SourceType, mapping.TargetType) is null)
{
_mappings.Add(new TypeMappingKey(mapping), mapping);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Emit;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors.Mappings.UserMappings;

/// <summary>
/// Represents an existing target type mapper which is implemented by the user.
/// </summary>
public class UserImplementedExistingTargetMethodMapping : ExistingTargetMapping, IUserMapping
{
private readonly MethodParameter _sourceParameter;
private readonly MethodParameter _targetParameter;
private readonly MethodParameter? _referenceHandlerParameter;

public UserImplementedExistingTargetMethodMapping(
IMethodSymbol method,
MethodParameter sourceParameter,
MethodParameter targetParameter,
MethodParameter? referenceHandlerParameter
)
: base(method.Parameters[0].Type.UpgradeNullable(), targetParameter.Type.UpgradeNullable())
{
Method = method;
_sourceParameter = sourceParameter;
_targetParameter = targetParameter;
_referenceHandlerParameter = referenceHandlerParameter;
}

public IMethodSymbol Method { get; }

public bool CallableByOtherMappings => true;
public bool IsSynthetic => false;

Check warning on line 37 in src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs#L37

Added line #L37 was not covered by tests

public ExpressionSyntax Build(TypeMappingBuildContext ctx) =>
throw new InvalidOperationException(
$"{nameof(UserImplementedExistingTargetMethodMapping)} {ctx.Source}, {ctx.ReferenceHandler} does not support {nameof(Build)}"
);

Check warning on line 42 in src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs#L40-L42

Added lines #L40 - L42 were not covered by tests

public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
// if the user implemented method is on an interface,
// we explicitly cast to be able to use the default interface implementation or explicit implementations
if (Method.ReceiverType?.TypeKind != TypeKind.Interface)
{
yield return SyntaxFactory.ExpressionStatement(
SyntaxFactoryHelper.Invocation(
Method.Name,
_sourceParameter.WithArgument(ctx.Source),
_targetParameter.WithArgument(target),
_referenceHandlerParameter?.WithArgument(ctx.ReferenceHandler)
)
);
yield break;
}

var castedThis = SyntaxFactory.CastExpression(
SyntaxFactoryHelper.FullyQualifiedIdentifier(Method.ReceiverType!),
SyntaxFactory.ThisExpression()
);
var method = SyntaxFactoryHelper.MemberAccess(SyntaxFactory.ParenthesizedExpression(castedThis), Method.Name);
yield return SyntaxFactory.ExpressionStatement(
SyntaxFactoryHelper.Invocation(
method,
_sourceParameter.WithArgument(ctx.Source),
_targetParameter.WithArgument(target),
_referenceHandlerParameter?.WithArgument(ctx.ReferenceHandler)
)
);
}
}
22 changes: 15 additions & 7 deletions src/Riok.Mapperly/Descriptors/UserMethodMappingExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,21 @@ private static IEnumerable<IMethodSymbol> ExtractBaseMethods(SimpleMappingBuilde
bool isStatic
)
{
var valid =
method is { ReturnsVoid: false, IsGenericMethod: false }
&& (allowPartial || !method.IsPartialDefinition)
&& (!isStatic || method.IsStatic);
return valid && BuildParameters(ctx, method, out var parameters)
? new UserImplementedMethodMapping(method, parameters.Source, parameters.ReferenceHandler)
: null;
var valid = !method.IsGenericMethod && (allowPartial || !method.IsPartialDefinition) && (!isStatic || method.IsStatic);

if (!valid || !BuildParameters(ctx, method, out var parameters))
{
return null;
}

return method.ReturnsVoid
? new UserImplementedExistingTargetMethodMapping(
method,
parameters.Source,
parameters.Target!.Value,
parameters.ReferenceHandler
)
: new UserImplementedMethodMapping(method, parameters.Source, parameters.ReferenceHandler);
}

private static IUserMapping? BuilderUserDefinedMapping(SimpleMappingBuilderContext ctx, IMethodSymbol methodSymbol, bool isStatic)
Expand Down
1 change: 1 addition & 0 deletions test/Riok.Mapperly.IntegrationTests/BaseMapperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public static TestObject NewTestObj()
ExistingISet = { "1", "2", "3", },
ExistingHashSet = { "1", "2", "3", },
ExistingSortedSet = { "1", "2", "3", },
ExistingList = { "1", "2", "3", },
ISet = new HashSet<string> { "1", "2", "3", },
#if NET5_0_OR_GREATER
IReadOnlySet = new HashSet<string> { "1", "2", "3", },
Expand Down
6 changes: 4 additions & 2 deletions test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ public TestObjectDto(int ctorValue, int unknownValue = 10, int ctorValue2 = 100)

public ISet<int> ExistingISet { get; } = new HashSet<int>();

public HashSet<int> ExistingHashSet { get; } = new HashSet<int>();
public HashSet<int> ExistingHashSet { get; } = new();

public SortedSet<int> ExistingSortedSet { get; } = new SortedSet<int>();
public SortedSet<int> ExistingSortedSet { get; } = new();

public List<int> ExistingList { get; } = new();

public ISet<int> ISet { get; set; } = new HashSet<int>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public static TestObjectDto MapToDto(TestObject src)
return target;
}

public static void MapExistingList(List<string> src, List<int> dst)
{
foreach (var item in src)
{
dst.Add(int.Parse(item));
}
}

// disable obsolete warning, as the obsolete attribute should still be tested.
#pragma warning disable CS0618
[MapperIgnore(nameof(TestObjectDto.IgnoredStringValue))]
Expand Down
6 changes: 4 additions & 2 deletions test/Riok.Mapperly.IntegrationTests/Models/TestObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ public TestObject(int ctorValue, int unknownValue = 10, int ctorValue2 = 100)

public ISet<string> ExistingISet { get; } = new HashSet<string>();

public HashSet<string> ExistingHashSet { get; } = new HashSet<string>();
public HashSet<string> ExistingHashSet { get; } = new();

public SortedSet<string> ExistingSortedSet { get; } = new SortedSet<string>();
public SortedSet<string> ExistingSortedSet { get; } = new();

public List<string> ExistingList { get; } = new();

public ISet<string> ISet { get; set; } = new HashSet<string>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@
2,
3
],
ExistingList: [
1,
2,
3
],
ISet: [
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public static partial class DeepCloningMapper
target.ExistingSortedSet.Add(item2);
}

foreach (var item3 in src.ExistingList)
{
target.ExistingList.Add(item3);
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(src.ISet);
target.HashSet = global::System.Linq.Enumerable.ToHashSet(src.HashSet);
target.SortedSet = new global::System.Collections.Generic.SortedSet<string>(src.SortedSet);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
2,
3
],
ExistingList: [
1,
2,
3
],
ISet: [
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ public partial int ParseableInt(string value)
target.ExistingSortedSet.Add(ParseableInt(item2));
}

foreach (var item3 in testObject.ExistingList)
{
target.ExistingList.Add(ParseableInt(item3));
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.ISet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.HashSet, x => ParseableInt(x)));
target.SortedSet = new global::System.Collections.Generic.SortedSet<int>(global::System.Linq.Enumerable.Select(testObject.SortedSet, x => ParseableInt(x)));
Expand Down Expand Up @@ -214,6 +219,11 @@ public partial int ParseableInt(string value)
target.ExistingSortedSet.Add(item2.ToString());
}

foreach (var item3 in dto.ExistingList)
{
target.ExistingList.Add(item3.ToString());
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.ISet, x => x.ToString()));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.HashSet, x => x.ToString()));
target.SortedSet = new global::System.Collections.Generic.SortedSet<string>(global::System.Linq.Enumerable.Select(dto.SortedSet, x => x.ToString()));
Expand Down Expand Up @@ -302,6 +312,11 @@ public partial void UpdateDto(global::Riok.Mapperly.IntegrationTests.Models.Test
target.ExistingSortedSet.Add(ParseableInt(item2));
}

foreach (var item3 in source.ExistingList)
{
target.ExistingList.Add(ParseableInt(item3));
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.ISet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.HashSet, x => ParseableInt(x)));
target.SortedSet = new global::System.Collections.Generic.SortedSet<int>(global::System.Linq.Enumerable.Select(source.SortedSet, x => ParseableInt(x)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@
2,
3
],
ExistingList: [
1,
2,
3
],
ISet: [
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
2,
3
],
ExistingList: [
1,
2,
3
],
ISet: [
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public static partial int ParseableInt(string value)
target.ExistingSortedSet.Add(ParseableInt(item2));
}

MapExistingList(src.ExistingList, target.ExistingList);
target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(src.ISet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(src.HashSet, x => ParseableInt(x)));
target.SortedSet = new global::System.Collections.Generic.SortedSet<int>(global::System.Linq.Enumerable.Select(src.SortedSet, x => ParseableInt(x)));
Expand Down Expand Up @@ -229,6 +230,7 @@ public static partial int ParseableInt(string value)
target.ExistingSortedSet.Add(ParseableInt(item2));
}

MapExistingList(testObject.ExistingList, target.ExistingList);
target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.ISet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.HashSet, x => ParseableInt(x)));
target.SortedSet = new global::System.Collections.Generic.SortedSet<int>(global::System.Linq.Enumerable.Select(testObject.SortedSet, x => ParseableInt(x)));
Expand Down Expand Up @@ -312,6 +314,11 @@ public static partial int ParseableInt(string value)
target.ExistingSortedSet.Add(item2.ToString());
}

foreach (var item3 in dto.ExistingList)
{
target.ExistingList.Add(item3.ToString());
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.ISet, x => x.ToString()));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.HashSet, x => x.ToString()));
target.SortedSet = new global::System.Collections.Generic.SortedSet<string>(global::System.Linq.Enumerable.Select(dto.SortedSet, x => x.ToString()));
Expand Down Expand Up @@ -400,6 +407,7 @@ public static partial void UpdateDto(global::Riok.Mapperly.IntegrationTests.Mode
target.ExistingSortedSet.Add(ParseableInt(item2));
}

MapExistingList(source.ExistingList, target.ExistingList);
target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.ISet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.HashSet, x => ParseableInt(x)));
target.SortedSet = new global::System.Collections.Generic.SortedSet<int>(global::System.Linq.Enumerable.Select(source.SortedSet, x => ParseableInt(x)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@
2,
3
],
ExistingList: [
1,
2,
3
],
ISet: [
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ public static partial class DeepCloningMapper
target.ExistingSortedSet.Add(item2);
}

target.ExistingList.EnsureCapacity(src.ExistingList.Count + target.ExistingList.Count);
foreach (var item3 in src.ExistingList)
{
target.ExistingList.Add(item3);
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(src.ISet);
target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(src.IReadOnlySet);
target.HashSet = global::System.Linq.Enumerable.ToHashSet(src.HashSet);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
2,
3
],
ExistingList: [
1,
2,
3
],
ISet: [
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ public partial int ParseableInt(string value)
target.ExistingSortedSet.Add(ParseableInt(item2));
}

target.ExistingList.EnsureCapacity(testObject.ExistingList.Count + target.ExistingList.Count);
foreach (var item3 in testObject.ExistingList)
{
target.ExistingList.Add(ParseableInt(item3));
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.ISet, x => ParseableInt(x)));
target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.IReadOnlySet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.HashSet, x => ParseableInt(x)));
Expand Down Expand Up @@ -211,6 +217,12 @@ public partial int ParseableInt(string value)
target.ExistingSortedSet.Add(item2.ToString());
}

target.ExistingList.EnsureCapacity(dto.ExistingList.Count + target.ExistingList.Count);
foreach (var item3 in dto.ExistingList)
{
target.ExistingList.Add(item3.ToString());
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.ISet, x => x.ToString()));
target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.IReadOnlySet, x => x.ToString()));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(dto.HashSet, x => x.ToString()));
Expand Down Expand Up @@ -301,6 +313,12 @@ public partial void UpdateDto(global::Riok.Mapperly.IntegrationTests.Models.Test
target.ExistingSortedSet.Add(ParseableInt(item2));
}

target.ExistingList.EnsureCapacity(source.ExistingList.Count + target.ExistingList.Count);
foreach (var item3 in source.ExistingList)
{
target.ExistingList.Add(ParseableInt(item3));
}

target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.ISet, x => ParseableInt(x)));
target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.IReadOnlySet, x => ParseableInt(x)));
target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.HashSet, x => ParseableInt(x)));
Expand Down
Loading

0 comments on commit 1cff8e3

Please sign in to comment.