From 9673fbb7d3661d336b979ad33bff5cb5154758aa Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sun, 19 Nov 2023 12:00:52 +0000 Subject: [PATCH] feat: add `MapDerivedType` for existing target type mapping --- .../DerivedTypeMappingBuilder.cs | 32 ++- .../ExistingTargetMappingBuilder.cs | 1 + .../DerivedExistingTargetTypeSwitchMapping.cs | 79 +++++++ .../Mappings/TypeMappingBuildContext.cs | 7 +- .../Syntax/SyntaxFactoryHelper.Pattern.cs | 7 + .../Emit/Syntax/SyntaxFactoryHelper.Switch.cs | 24 ++ .../Mapping/DerivedExistingTargetTypeTest.cs | 212 ++++++++++++++++++ ...icInterfaceShouldWork#Mapper.g.verified.cs | 22 ++ ...ctBaseClassShouldWork#Mapper.g.verified.cs | 22 ++ ...eTypeConfigShouldWork#Mapper.g.verified.cs | 22 ++ ...thInterfaceShouldWork#Mapper.g.verified.cs | 22 ++ ...getNullableShouldWork#Mapper.g.verified.cs | 24 ++ ...rceNullableShouldWork#Mapper.g.verified.cs | 24 ++ 13 files changed, 492 insertions(+), 6 deletions(-) create mode 100644 src/Riok.Mapperly/Descriptors/Mappings/DerivedExistingTargetTypeSwitchMapping.cs create mode 100644 test/Riok.Mapperly.Tests/Mapping/DerivedExistingTargetTypeTest.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassAndNonGenericInterfaceShouldWork#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassShouldWork#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithBaseTypeConfigShouldWork#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceShouldWork#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceAndTargetNullableShouldWork#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceNullableShouldWork#Mapper.g.verified.cs diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/DerivedTypeMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/DerivedTypeMappingBuilder.cs index 9f9455547d..8eb25fcbdb 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/DerivedTypeMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/DerivedTypeMappingBuilder.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis; using Riok.Mapperly.Configuration; using Riok.Mapperly.Descriptors.Mappings; +using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; using Riok.Mapperly.Diagnostics; using Riok.Mapperly.Helpers; @@ -19,6 +20,12 @@ public static class DerivedTypeMappingBuilder : new DerivedTypeSwitchMapping(ctx.Source, ctx.Target, derivedTypeMappings); } + public static IExistingTargetMapping? TryBuildExistingTargetMapping(MappingBuilderContext ctx) + { + var derivedTypeMappings = TryBuildExistingTargetContainedMappings(ctx); + return derivedTypeMappings == null ? null : new DerivedExistingTargetTypeSwitchMapping(ctx.Source, ctx.Target, derivedTypeMappings); + } + public static IReadOnlyCollection? TryBuildContainedMappings( MappingBuilderContext ctx, bool duplicatedSourceTypesAllowed = false @@ -26,17 +33,34 @@ public static class DerivedTypeMappingBuilder { return ctx.Configuration.DerivedTypes.Count == 0 ? null - : BuildContainedMappings(ctx, ctx.Configuration.DerivedTypes, duplicatedSourceTypesAllowed); + : BuildContainedMappings(ctx, ctx.Configuration.DerivedTypes, ctx.FindOrBuildMapping, duplicatedSourceTypesAllowed); + } + + private static IReadOnlyCollection? TryBuildExistingTargetContainedMappings( + MappingBuilderContext ctx, + bool duplicatedSourceTypesAllowed = false + ) + { + return ctx.Configuration.DerivedTypes.Count == 0 + ? null + : BuildContainedMappings( + ctx, + ctx.Configuration.DerivedTypes, + (source, target, options, _) => ctx.FindOrBuildExistingTargetMapping(source, target, options), + duplicatedSourceTypesAllowed + ); } - private static IReadOnlyCollection BuildContainedMappings( + private static IReadOnlyCollection BuildContainedMappings( MappingBuilderContext ctx, IReadOnlyCollection configs, + Func mappingFunction, bool duplicatedSourceTypesAllowed ) + where TMapping : ITypeMapping { var derivedTypeMappingSourceTypes = new HashSet(SymbolEqualityComparer.Default); - var derivedTypeMappings = new List(configs.Count); + var derivedTypeMappings = new List(configs.Count); Func isAssignableToSource = ctx.Source is ITypeParameterSymbol sourceTypeParameter ? t => ctx.SymbolAccessor.DoesTypeSatisfyTypeParameterConstraints(sourceTypeParameter, t, ctx.Source.NullableAnnotation) : t => ctx.SymbolAccessor.HasImplicitConversion(t, ctx.Source); @@ -67,7 +91,7 @@ bool duplicatedSourceTypesAllowed continue; } - var mapping = ctx.FindOrBuildMapping( + var mapping = mappingFunction( sourceType, targetType, MappingBuildingOptions.KeepUserSymbol | MappingBuildingOptions.MarkAsReusable | MappingBuildingOptions.ClearDerivedTypes, diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs index b7ec867ac2..a49790bbaa 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs @@ -9,6 +9,7 @@ public class ExistingTargetMappingBuilder(MappingCollection mappings) private static readonly IReadOnlyCollection _builders = new BuildExistingTargetMapping[] { NullableMappingBuilder.TryBuildExistingTargetMapping, + DerivedTypeMappingBuilder.TryBuildExistingTargetMapping, DictionaryMappingBuilder.TryBuildExistingTargetMapping, SpanMappingBuilder.TryBuildExistingTargetMapping, MemoryMappingBuilder.TryBuildExistingTargetMapping, diff --git a/src/Riok.Mapperly/Descriptors/Mappings/DerivedExistingTargetTypeSwitchMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/DerivedExistingTargetTypeSwitchMapping.cs new file mode 100644 index 0000000000..0b43081ead --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Mappings/DerivedExistingTargetTypeSwitchMapping.cs @@ -0,0 +1,79 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; +using Riok.Mapperly.Emit.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper; + +namespace Riok.Mapperly.Descriptors.Mappings; + +/// +/// A derived type mapping maps one base type or interface to another +/// by implementing a switch statement over known types and performs the provided mapping for each type. +/// +public class DerivedExistingTargetTypeSwitchMapping (ITypeSymbol sourceType, + ITypeSymbol targetType, + IReadOnlyCollection existingTargetTypeMappings) + : ExistingTargetMapping(sourceType, targetType) +{ + private const string SourceName = "source"; + private const string TargetName = "target"; + private const string GetTypeMethodName = nameof(GetType); + + public override IEnumerable Build(TypeMappingBuildContext ctx, ExpressionSyntax target) + { + var sourceExpression = TupleExpression(CommaSeparatedList(Argument(ctx.Source), Argument(target))); + var caseSections = existingTargetTypeMappings.Select(x => BuildSwitchSection(ctx, x)); + var defaultSection = BuildDefaultSwitchSection(ctx, target); + + yield return ctx.SyntaxFactory.SwitchStatement(sourceExpression, caseSections, defaultSection).AddLeadingLineFeed(ctx.SyntaxFactory.Indentation); + } + + private SwitchSectionSyntax BuildSwitchSection(TypeMappingBuildContext ctx, IExistingTargetMapping mapping) + { + var (sectionCtx, sourceVariableName) = ctx.WithNewScopedSource(IdentifierName, SourceName); + var targetVariableName = sectionCtx.NameBuilder.New(TargetName); + sectionCtx = sectionCtx.AddIndentation(); + + // (A source, B target) + var positionalTypeMatch = PositionalPatternClause( + CommaSeparatedList( + Subpattern(DeclarationPattern(mapping.SourceType, sourceVariableName)), + Subpattern(DeclarationPattern(mapping.TargetType, targetVariableName)) + ) + ); + var pattern = RecursivePattern().WithPositionalPatternClause(positionalTypeMatch); + + // case (A source, B target): + var caseLabel = CasePatternSwitchLabel(pattern).AddLeadingLineFeed(sectionCtx.SyntaxFactory.Indentation); + + // break; + var statementContext = sectionCtx.AddIndentation(); + var breakStatement = BreakStatement().AddLeadingLineFeed(statementContext.SyntaxFactory.Indentation); + var target = IdentifierName(targetVariableName); + var statements = mapping.Build(statementContext, target).Append(breakStatement); + + return SwitchSection(caseLabel, statements); + } + + private SwitchSectionSyntax BuildDefaultSwitchSection(TypeMappingBuildContext ctx, ExpressionSyntax target) + { + // default: + var sectionCtx = ctx.SyntaxFactory.AddIndentation(); + var defaultCaseLabel = DefaultSwitchLabel().AddLeadingLineFeed(sectionCtx.Indentation); + + // throw new ArgumentException(msg, nameof(ctx.Source)), + var sourceType = Invocation(MemberAccess(ctx.Source, GetTypeMethodName)); + var targetType = Invocation(MemberAccess(target, GetTypeMethodName)); + var statementContext = sectionCtx.AddIndentation(); + var throwExpression = ThrowArgumentExpression( + InterpolatedString($"Cannot map {sourceType} to {targetType} as there is no known derived type mapping"), + ctx.Source + ) + .AddLeadingLineFeed(statementContext.Indentation); + + var statements = new StatementSyntax[] { ExpressionStatement(throwExpression) }; + + return SwitchSection(defaultCaseLabel, statements); + } +} diff --git a/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs b/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs index afadf1584d..1ce57e3c90 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs @@ -55,10 +55,13 @@ SyntaxFactoryHelper syntaxFactory /// /// A function to build the source access for the new context. /// The new context and the scoped name of the source. - public (TypeMappingBuildContext Context, string SourceName) WithNewScopedSource(Func sourceBuilder) + public (TypeMappingBuildContext Context, string SourceName) WithNewScopedSource( + Func sourceBuilder, + string sourceName = DefaultSourceName + ) { var scopedNameBuilder = NameBuilder.NewScope(); - var scopedSourceName = scopedNameBuilder.New(DefaultSourceName); + var scopedSourceName = scopedNameBuilder.New(sourceName); var ctx = new TypeMappingBuildContext(sourceBuilder(scopedSourceName), ReferenceHandler, scopedNameBuilder, SyntaxFactory); return (ctx, scopedSourceName); } diff --git a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Pattern.cs b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Pattern.cs index 4bad9249ca..1322a1880b 100644 --- a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Pattern.cs +++ b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Pattern.cs @@ -1,3 +1,4 @@ +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Riok.Mapperly.Helpers; @@ -16,6 +17,12 @@ public static PatternSyntax OrPattern(IEnumerable values) => public static IsPatternExpressionSyntax IsPattern(ExpressionSyntax expression, PatternSyntax pattern) => IsPatternExpression(expression, SpacedToken(SyntaxKind.IsKeyword), pattern); + public static DeclarationPatternSyntax DeclarationPattern(ITypeSymbol type, string designation) => + SyntaxFactory.DeclarationPattern( + FullyQualifiedIdentifier(type).AddTrailingSpace(), + SingleVariableDesignation(Identifier(designation)) + ); + private static BinaryPatternSyntax BinaryPattern(SyntaxKind kind, PatternSyntax left, PatternSyntax right) { var binaryPattern = SyntaxFactory.BinaryPattern(kind, left, right); diff --git a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Switch.cs b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Switch.cs index dfcc32c433..a9be6cb753 100644 --- a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Switch.cs +++ b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Switch.cs @@ -23,4 +23,28 @@ public static SwitchExpressionArmSyntax SwitchArm(PatternSyntax pattern, Express } public static WhenClauseSyntax SwitchWhen(ExpressionSyntax condition) => WhenClause(SpacedToken(SyntaxKind.WhenKeyword), condition); + + public SwitchStatementSyntax SwitchStatement( + ExpressionSyntax governingExpression, + IEnumerable sections, + SwitchSectionSyntax defaultSection + ) + { + return SyntaxFactory.SwitchStatement( + default, + TrailingSpacedToken(SyntaxKind.SwitchKeyword), + Token(SyntaxKind.None), + governingExpression, + Token(SyntaxKind.None), + LeadingLineFeedToken(SyntaxKind.OpenBraceToken), + List(sections.Append(defaultSection)), + LeadingLineFeedToken(SyntaxKind.CloseBraceToken) + ); + } + + public static SwitchSectionSyntax SwitchSection(SwitchLabelSyntax labelSyntax, IEnumerable statements) => + SyntaxFactory.SwitchSection().WithLabels(SingletonList(labelSyntax)).WithStatements(List(statements)); + + public static CasePatternSwitchLabelSyntax CasePatternSwitchLabel(PatternSyntax pattern) => + SyntaxFactory.CasePatternSwitchLabel(TrailingSpacedToken(SyntaxKind.CaseKeyword), pattern, null, Token(SyntaxKind.ColonToken)); } diff --git a/test/Riok.Mapperly.Tests/Mapping/DerivedExistingTargetTypeTest.cs b/test/Riok.Mapperly.Tests/Mapping/DerivedExistingTargetTypeTest.cs new file mode 100644 index 0000000000..79d4fdf330 --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/DerivedExistingTargetTypeTest.cs @@ -0,0 +1,212 @@ +using Riok.Mapperly.Diagnostics; + +namespace Riok.Mapperly.Tests.Mapping; + +[UsesVerify] +public class DerivedExistingTargetTypeTest +{ + [Fact] + public Task WithAbstractBaseClassShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + [MapDerivedType] + public partial void Map(A src, B trg); + """, + "abstract class A { public string BaseValue { get; set; } }", + "abstract class B { public string BaseValue { get; set; } }", + "class ASubType1 : A { public string Value1 { get; set; } }", + "class ASubType2 : A { public string Value1 { get; set; } }", + "class BSubType1 : B { public string Value1 { get; set; } }", + "class BSubType2 : B { public string Value1 { get; set; } }" + ); + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task WithAbstractBaseClassAndNonGenericInterfaceShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType(typeof(ASubType1), typeof(BSubType1))] + [MapDerivedType(typeof(ASubType2), typeof(BSubType2))] + public partial void Map(A src, B trg); + """, + "abstract class A { public string BaseValue { get; set; } }", + "abstract class B { public string BaseValue { get; set; } }", + "class ASubType1 : A { public string Value1 { get; set; } }", + "class ASubType2 : A { public string Value2 { get; set; } }", + "class BSubType1 : B { public string Value1 { get; set; } }", + "class BSubType2 : B { public string Value2 { get; set; } }" + ); + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task WithInterfaceShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + [MapDerivedType] + public partial void Map(A src, B trg); + """, + "interface A { string BaseValue { get; set; } }", + "interface B { string BaseValue { get; set; }}", + "class AImpl1 : A { public string BaseValue { get; set; } public string Value1 { get; set; } }", + "class AImpl2 : A { public string BaseValue { get; set; } public string Value2 { get; set; } }", + "class BImpl1 : B { public string BaseValue { get; set; } public string Value1 { get; set; } }", + "class BImpl2 : B { public string BaseValue { get; set; } public string Value2 { get; set; } }" + ); + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task WithInterfaceSourceNullableShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + [MapDerivedType] + public partial void Map(A? src, B trg); + """, + "interface A { string BaseValue { get; set; } }", + "interface B { string BaseValue { get; set; }}", + "class AImpl1 : A { public string BaseValue { get; set; } public string Value1 { get; set; } }", + "class AImpl2 : A { public string BaseValue { get; set; } public string Value2 { get; set; } }", + "class BImpl1 : B { public string BaseValue { get; set; } public string Value1 { get; set; } }", + "class BImpl2 : B { public string BaseValue { get; set; } public string Value2 { get; set; } }" + ); + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task WithInterfaceSourceAndTargetNullableShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + [MapDerivedType] + public partial void Map(A? src, B? trg); + """, + "interface A { string BaseValue { get; set; } }", + "interface B { string BaseValue { get; set; }}", + "class AImpl1 : A { public string BaseValue { get; set; } public string Value1 { get; set; } }", + "class AImpl2 : A { public string BaseValue { get; set; } public string Value2 { get; set; } }", + "class BImpl1 : B { public string BaseValue { get; set; } public string Value1 { get; set; } }", + "class BImpl2 : B { public string BaseValue { get; set; } public string Value2 { get; set; } }" + ); + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public void NotAssignableTargetTypeShouldDiagnostic() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + public partial void Map(A src, B trg); + """, + "interface A {}", + "interface B {}", + "class AImpl1 : A { }", + "class BImpl1 { }" + ); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic( + DiagnosticDescriptors.DerivedTargetTypeIsNotAssignableToReturnType, + "Derived target type BImpl1 is not assignable to return type B" + ) + .HaveAssertedAllDiagnostics(); + } + + [Fact] + public Task WithBaseTypeConfigShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + [MapDerivedType] + [MapProperty(nameof(A.BaseValueA), nameof(B.BaseValueB)] + public partial void Map(A src, B trg); + """, + "abstract class A { public string BaseValueA { get; set; } }", + "abstract class B { public string BaseValueB { get; set; } }", + "class ASubType1 : A { public string Value1 { get; set; } }", + "class ASubType2 : A { public string Value2 { get; set; } }", + "class BSubType1 : B { public string Value1 { get; set; } }", + "class BSubType2 : B { public string Value2 { get; set; } }" + ); + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public void NotAssignableSourceTypeShouldDiagnostic() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + public partial void Map(A src, B trg); + """, + "interface A {}", + "interface B {}", + "class AImpl1 { }", + "class BImpl1 : B { }" + ); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic( + DiagnosticDescriptors.DerivedSourceTypeIsNotAssignableToParameterType, + "Derived source type AImpl1 is not assignable to parameter type A" + ) + .HaveAssertedAllDiagnostics(); + } + + [Fact] + public void DuplicatedSourceTypeShouldDiagnostic() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + [MapDerivedType] + public partial void Map(A src, B trg); + """, + "interface A {}", + "interface B {}", + "class AImpl1 : A { }", + "class BImpl1 : B { }", + "class BImpl2 : B { }" + ); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic( + DiagnosticDescriptors.DerivedSourceTypeDuplicated, + "Derived source type AImpl1 is specified multiple times, a source type may only be specified once" + ) + .HaveAssertedAllDiagnostics(); + } + + [Fact] + public void NotMappableShouldDiagnostic() + { + var source = TestSourceBuilder.MapperWithBody( + """ + [MapDerivedType] + public partial void Map(object src, object trg); + """ + ); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic( + DiagnosticDescriptors.CouldNotCreateMapping, + "Could not create mapping from System.Version to int. Consider implementing the mapping manually." + ) + .HaveAssertedAllDiagnostics(); + } +} diff --git a/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassAndNonGenericInterfaceShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassAndNonGenericInterfaceShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..c5a1463bf0 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassAndNonGenericInterfaceShouldWork#Mapper.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + public partial void Map(global::A src, global::B trg) + { + switch (src, trg) + { + case (global::ASubType1 source, global::BSubType1 target): + target.Value1 = source.Value1; + target.BaseValue = source.BaseValue; + break; + case (global::ASubType2 source, global::BSubType2 target): + target.Value2 = source.Value2; + target.BaseValue = source.BaseValue; + break; + default: + throw new System.ArgumentException($"Cannot map {src.GetType()} to {trg.GetType()} as there is no known derived type mapping", nameof(src)); + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..ad567c88d3 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithAbstractBaseClassShouldWork#Mapper.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + public partial void Map(global::A src, global::B trg) + { + switch (src, trg) + { + case (global::ASubType1 source, global::BSubType1 target): + target.Value1 = source.Value1; + target.BaseValue = source.BaseValue; + break; + case (global::ASubType2 source, global::BSubType2 target): + target.Value1 = source.Value1; + target.BaseValue = source.BaseValue; + break; + default: + throw new System.ArgumentException($"Cannot map {src.GetType()} to {trg.GetType()} as there is no known derived type mapping", nameof(src)); + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithBaseTypeConfigShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithBaseTypeConfigShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..02bec65def --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithBaseTypeConfigShouldWork#Mapper.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + public partial void Map(global::A src, global::B trg) + { + switch (src, trg) + { + case (global::ASubType1 source, global::BSubType1 target): + target.Value1 = source.Value1; + target.BaseValueB = source.BaseValueA; + break; + case (global::ASubType2 source, global::BSubType2 target): + target.Value2 = source.Value2; + target.BaseValueB = source.BaseValueA; + break; + default: + throw new System.ArgumentException($"Cannot map {src.GetType()} to {trg.GetType()} as there is no known derived type mapping", nameof(src)); + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..8b23fa146f --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceShouldWork#Mapper.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + public partial void Map(global::A src, global::B trg) + { + switch (src, trg) + { + case (global::AImpl1 source, global::BImpl1 target): + target.BaseValue = source.BaseValue; + target.Value1 = source.Value1; + break; + case (global::AImpl2 source, global::BImpl2 target): + target.BaseValue = source.BaseValue; + target.Value2 = source.Value2; + break; + default: + throw new System.ArgumentException($"Cannot map {src.GetType()} to {trg.GetType()} as there is no known derived type mapping", nameof(src)); + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceAndTargetNullableShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceAndTargetNullableShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..c1f830d413 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceAndTargetNullableShouldWork#Mapper.g.verified.cs @@ -0,0 +1,24 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + public partial void Map(global::A? src, global::B? trg) + { + if (src == null || trg == null) + return; + switch (src, trg) + { + case (global::AImpl1 source, global::BImpl1 target): + target.BaseValue = source.BaseValue; + target.Value1 = source.Value1; + break; + case (global::AImpl2 source, global::BImpl2 target): + target.BaseValue = source.BaseValue; + target.Value2 = source.Value2; + break; + default: + throw new System.ArgumentException($"Cannot map {src.GetType()} to {trg.GetType()} as there is no known derived type mapping", nameof(src)); + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceNullableShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceNullableShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..42f847da81 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/DerivedExistingTargetTypeTest.WithInterfaceSourceNullableShouldWork#Mapper.g.verified.cs @@ -0,0 +1,24 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + public partial void Map(global::A? src, global::B trg) + { + if (src == null) + return; + switch (src, trg) + { + case (global::AImpl1 source, global::BImpl1 target): + target.BaseValue = source.BaseValue; + target.Value1 = source.Value1; + break; + case (global::AImpl2 source, global::BImpl2 target): + target.BaseValue = source.BaseValue; + target.Value2 = source.Value2; + break; + default: + throw new System.ArgumentException($"Cannot map {src.GetType()} to {trg.GetType()} as there is no known derived type mapping", nameof(src)); + } + } +} \ No newline at end of file