diff --git a/src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs b/src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs new file mode 100644 index 00000000..0bb500c9 --- /dev/null +++ b/src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; + +namespace Mapster.Tests +{ + /// + /// https://github.com/MapsterMapper/Mapster/issues/947 + /// + [TestClass] + public class WhenImplicitInheritanceMapWithDerivedDestination + { + [TestCleanup] + public void Cleanup() + { + TypeAdapterConfig.GlobalSettings.Clear(); + TypeAdapterConfig.GlobalSettings.AllowImplicitDestinationInheritance = false; + } + + [TestMethod] + public void Inherited_MapWith_On_Base_Destination_Casts_To_Derived_Destination() + { + var config = new TypeAdapterConfig(); + config.AllowImplicitDestinationInheritance = true; + config.NewConfig() + .MapWith(src => src.Type == "Bird" + ? (Animal947)new Bird947 { AnimalValue = src.AnimalValueDto } + : new Dog947 { AnimalValue = src.AnimalValueDto }); + + var source = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" }; + + var dog = source.Adapt(config); + + dog.ShouldBeOfType(); + dog.AnimalValue.ShouldBe("Hello"); + } + + [TestMethod] + public void Inherited_MapWith_Works_For_Explicit_Source_Destination_Pair() + { + var config = new TypeAdapterConfig(); + config.AllowImplicitDestinationInheritance = true; + config.NewConfig() + .MapWith(src => src.Type == "Bird" + ? (Animal947)new Bird947 { AnimalValue = src.AnimalValueDto } + : new Dog947 { AnimalValue = src.AnimalValueDto }); + + var source = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" }; + + var dog = source.Adapt(config); + + dog.ShouldBeOfType(); + dog.AnimalValue.ShouldBe("Hello"); + } + + public abstract class Animal947 + { + public string AnimalValue { get; set; } = null!; + } + + public class Dog947 : Animal947 + { + } + + public class Bird947 : Animal947 + { + } + + public class AnimalDto947 + { + public string AnimalValueDto { get; set; } = null!; + + public string Type { get; set; } = null!; + } + } +} diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 1dce2053..af88d5bd 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -431,7 +431,7 @@ private static LambdaExpression CreateMapExpression(CompileArgument arg) throw new CompileException(arg, new InvalidOperationException("ConverterFactory is not found")); try { - return fn(arg); + return AdjustInheritedConverterReturnType(fn(arg), arg); } catch (Exception ex) { @@ -439,6 +439,22 @@ private static LambdaExpression CreateMapExpression(CompileArgument arg) } } + private static LambdaExpression AdjustInheritedConverterReturnType(LambdaExpression lambda, CompileArgument arg) + { + var destinationType = arg.DestinationType; + var returnType = lambda.ReturnType; + if (returnType == destinationType) + return lambda; + + // MapWith configured on a base destination type returns the base type, but implicit + // destination inheritance can compile the converter for a derived destination. + if (!returnType.IsAssignableFrom(destinationType)) + return lambda; + + var body = lambda.Body.To(destinationType, force: true); + return Expression.Lambda(body, lambda.Parameters); + } + private LambdaExpression CreateDynamicMapExpression(TypeTuple tuple) { var lambda = CreateMapExpression(tuple, MapType.Map);