diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs index a78299696f..f826c1cdca 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs @@ -11,7 +11,7 @@ public class MappingBuilder(MappingCollection mappings) { NullableMappingBuilder.TryBuildMapping, DerivedTypeMappingBuilder.TryBuildMapping, - SpecialTypeMappingBuilder.TryBuildMapping, + ToObjectMappingBuilder.TryBuildMapping, DirectAssignmentMappingBuilder.TryBuildMapping, QueryableMappingBuilder.TryBuildMapping, DictionaryMappingBuilder.TryBuildMapping, diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectMemberMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectMemberMappingBuilder.cs index 1acd0a1c8d..6b3f8fc760 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectMemberMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectMemberMappingBuilder.cs @@ -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, diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/SpecialTypeMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/SpecialTypeMappingBuilder.cs deleted file mode 100644 index 7ba6e0909a..0000000000 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/SpecialTypeMappingBuilder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.CodeAnalysis; -using Riok.Mapperly.Abstractions; -using Riok.Mapperly.Descriptors.Mappings; -using Riok.Mapperly.Diagnostics; - -namespace Riok.Mapperly.Descriptors.MappingBuilders; - -public static class SpecialTypeMappingBuilder -{ - public static NewInstanceMapping? TryBuildMapping(MappingBuilderContext ctx) - { - if (!ctx.IsConversionEnabled(MappingConversionType.ExplicitCast)) - return null; - - return ctx.Target.SpecialType switch - { - SpecialType.System_Object when ctx.MapperConfiguration.UseDeepCloning && ctx.Source.SpecialType == SpecialType.System_Object - => BuildDeepCloneObjectToObjectMapping(ctx), - SpecialType.System_Object when ctx.MapperConfiguration.UseDeepCloning - => new CastMapping(ctx.Source, ctx.Target, ctx.FindOrBuildMapping(ctx.Source, ctx.Source)), - SpecialType.System_Object => new CastMapping(ctx.Source, ctx.Target), - _ => null, - }; - } - - private static DirectAssignmentMapping BuildDeepCloneObjectToObjectMapping(MappingBuilderContext ctx) - { - ctx.ReportDiagnostic(DiagnosticDescriptors.MappedObjectToObjectWithoutDeepClone, ctx.Source.Name, ctx.Target.Name); - return new DirectAssignmentMapping(ctx.Source); - } -} diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/ToObjectMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/ToObjectMappingBuilder.cs new file mode 100644 index 0000000000..914c735332 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/ToObjectMappingBuilder.cs @@ -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)); + } +} diff --git a/src/Riok.Mapperly/Helpers/SymbolExtensions.cs b/src/Riok.Mapperly/Helpers/SymbolExtensions.cs index a7c19f33cb..00b691e355 100644 --- a/src/Riok.Mapperly/Helpers/SymbolExtensions.cs +++ b/src/Riok.Mapperly/Helpers/SymbolExtensions.cs @@ -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) { diff --git a/test/Riok.Mapperly.Tests/Mapping/DelegateTest.cs b/test/Riok.Mapperly.Tests/Mapping/DelegateTest.cs new file mode 100644 index 0000000000..831a2bb7e5 --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/DelegateTest.cs @@ -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", "Action"); + TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;"); + } + + [Fact] + public void ActionToActionWithDeepCloning() + { + var source = TestSourceBuilder.Mapping("Action", "Action", TestSourceBuilderOptions.WithDeepCloning); + TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;"); + } + + [Fact] + public void FuncToFunc() + { + var source = TestSourceBuilder.Mapping("Func", "Func"); + TestHelper.GenerateMapper(source).Should().HaveMapMethodBody("return source;"); + } + + [Fact] + public void FuncToFuncWithDeepCloning() + { + var source = TestSourceBuilder.Mapping("Func", "Func", 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", "X", "delegate string X(string value);"); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic(DiagnosticDescriptors.CouldNotCreateMapping) + .HaveAssertedAllDiagnostics(); + } +} diff --git a/test/Riok.Mapperly.Tests/Mapping/SpecialTypeDeepCloningTest.cs b/test/Riok.Mapperly.Tests/Mapping/ToObjectDeepCloningTest.cs similarity index 98% rename from test/Riok.Mapperly.Tests/Mapping/SpecialTypeDeepCloningTest.cs rename to test/Riok.Mapperly.Tests/Mapping/ToObjectDeepCloningTest.cs index af97f3f9a7..502b1352b1 100644 --- a/test/Riok.Mapperly.Tests/Mapping/SpecialTypeDeepCloningTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ToObjectDeepCloningTest.cs @@ -2,7 +2,7 @@ namespace Riok.Mapperly.Tests.Mapping; -public class SpecialTypeDeepCloningTest +public class ToObjectDeepCloningTest { [Fact] public void ClassToObjectDeepCloning() diff --git a/test/Riok.Mapperly.Tests/Mapping/SpecialTypeTest.cs b/test/Riok.Mapperly.Tests/Mapping/ToObjectTypeTest.cs similarity index 97% rename from test/Riok.Mapperly.Tests/Mapping/SpecialTypeTest.cs rename to test/Riok.Mapperly.Tests/Mapping/ToObjectTypeTest.cs index bed912014f..4ce805148a 100644 --- a/test/Riok.Mapperly.Tests/Mapping/SpecialTypeTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ToObjectTypeTest.cs @@ -1,6 +1,6 @@ namespace Riok.Mapperly.Tests.Mapping; -public class SpecialTypeTest +public class ToObjectTypeTest { [Fact] public void ClassToObject()