Skip to content

Commit

Permalink
fix: prevent crash for delegate fields when deep mapping is enabled (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz authored Jan 10, 2024
1 parent ad917ee commit 88dd1ff
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class MappingBuilder(MappingCollection mappings)
{
NullableMappingBuilder.TryBuildMapping,
DerivedTypeMappingBuilder.TryBuildMapping,
SpecialTypeMappingBuilder.TryBuildMapping,
ToObjectMappingBuilder.TryBuildMapping,
DirectAssignmentMappingBuilder.TryBuildMapping,
QueryableMappingBuilder.TryBuildMapping,
DictionaryMappingBuilder.TryBuildMapping,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public static class NewInstanceObjectMemberMappingBuilder
if (ctx.Target.SpecialType != SpecialType.None || ctx.Source.SpecialType != SpecialType.None)
return null;

if (ctx.Source.IsDelegate() || ctx.Target.IsDelegate())
return null;

if (ctx.ObjectFactories.TryFindObjectFactory(ctx.Source, ctx.Target, out var objectFactory))
return new NewInstanceObjectFactoryMemberMapping(
ctx.Source,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Diagnostics;

namespace Riok.Mapperly.Descriptors.MappingBuilders;

public static class ToObjectMappingBuilder
{
public static NewInstanceMapping? TryBuildMapping(MappingBuilderContext ctx)
{
if (!ctx.IsConversionEnabled(MappingConversionType.ExplicitCast))
return null;

if (ctx.Target.SpecialType != SpecialType.System_Object)
return null;

if (!ctx.MapperConfiguration.UseDeepCloning)
return new CastMapping(ctx.Source, ctx.Target);

if (ctx.Source.SpecialType == SpecialType.System_Object)
{
ctx.ReportDiagnostic(DiagnosticDescriptors.MappedObjectToObjectWithoutDeepClone, ctx.Source.Name, ctx.Target.Name);
return new DirectAssignmentMapping(ctx.Source);
}

return new CastMapping(ctx.Source, ctx.Target, ctx.FindOrBuildMapping(ctx.Source, ctx.Source));
}
}
23 changes: 17 additions & 6 deletions src/Riok.Mapperly/Helpers/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,24 @@ internal static class SymbolExtensions
internal static string FullyQualifiedIdentifierName(this ITypeSymbol typeSymbol) =>
typeSymbol.ToDisplayString(_fullyQualifiedNullableFormat);

internal static bool IsImmutable(this ISymbol symbol) =>
symbol is INamedTypeSymbol namedSymbol
&& (
namedSymbol.IsUnmanagedType
internal static bool IsImmutable(this ISymbol symbol)
{
if (symbol is not INamedTypeSymbol namedSymbol)
return false;

return namedSymbol.IsUnmanagedType
|| namedSymbol.SpecialType == SpecialType.System_String
|| _wellKnownImmutableTypes.Contains(namedSymbol.ToDisplayString())
);
|| IsDelegate(symbol)
|| _wellKnownImmutableTypes.Contains(namedSymbol.ToDisplayString());
}

internal static bool IsDelegate(this ISymbol symbol)
{
if (symbol is not INamedTypeSymbol namedSymbol)
return false;

return namedSymbol.DelegateInvokeMethod != null;
}

internal static int GetInheritanceLevel(this ITypeSymbol symbol)
{
Expand Down
59 changes: 59 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/DelegateTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Riok.Mapperly.Diagnostics;

namespace Riok.Mapperly.Tests.Mapping;

public class DelegateTest
{
[Fact]
public void ActionToAction()
{
var source = TestSourceBuilder.Mapping("Action<string>", "Action<string>");
TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;");
}

[Fact]
public void ActionToActionWithDeepCloning()
{
var source = TestSourceBuilder.Mapping("Action<string>", "Action<string>", TestSourceBuilderOptions.WithDeepCloning);
TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;");
}

[Fact]
public void FuncToFunc()
{
var source = TestSourceBuilder.Mapping("Func<string, string>", "Func<string, string>");
TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;");
}

[Fact]
public void FuncToFuncWithDeepCloning()
{
var source = TestSourceBuilder.Mapping("Func<string>", "Func<string>", TestSourceBuilderOptions.WithDeepCloning);
TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;");
}

[Fact]
public void CustomDelegateToCustomDelegate()
{
var source = TestSourceBuilder.Mapping("X", "X", "delegate string X(string value);");
TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;");
}

[Fact]
public void CustomDelegateToCustomDelegateWithDeepCloning()
{
var source = TestSourceBuilder.Mapping("X", "X", TestSourceBuilderOptions.WithDeepCloning, "delegate string X(string value);");
TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;");
}

[Fact]
public void FuncToCustomDelegateShouldDiagnostic()
{
var source = TestSourceBuilder.Mapping("Func<string, string>", "X", "delegate string X(string value);");
TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.CouldNotCreateMapping)
.HaveAssertedAllDiagnostics();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Riok.Mapperly.Tests.Mapping;

public class SpecialTypeDeepCloningTest
public class ToObjectDeepCloningTest
{
[Fact]
public void ClassToObjectDeepCloning()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Riok.Mapperly.Tests.Mapping;

public class SpecialTypeTest
public class ToObjectTypeTest
{
[Fact]
public void ClassToObject()
Expand Down

0 comments on commit 88dd1ff

Please sign in to comment.