diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/DerivedTypeMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/DerivedTypeMappingBuilder.cs index 1ebf6798c8..2aa4c7fb32 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 DerivedExistingTargetTypeMapping(ctx.Source, ctx.Target, derivedTypeMappings); + } + public static IReadOnlyCollection? TryBuildContainedMappings( MappingBuilderContext ctx, bool duplicatedSourceTypesAllowed = false @@ -29,6 +36,16 @@ public static class DerivedTypeMappingBuilder : BuildContainedMappings(ctx, ctx.Configuration.DerivedTypes, duplicatedSourceTypesAllowed); } + private static IReadOnlyCollection? TryBuildExistingTargetContainedMappings( + MappingBuilderContext ctx, + bool duplicatedSourceTypesAllowed = false + ) + { + return ctx.Configuration.DerivedTypes.Count == 0 + ? null + : BuildExistingTargetContainedMappings(ctx, ctx.Configuration.DerivedTypes, duplicatedSourceTypesAllowed); + } + private static IReadOnlyCollection BuildContainedMappings( MappingBuilderContext ctx, IReadOnlyCollection configs, @@ -83,4 +100,59 @@ bool duplicatedSourceTypesAllowed return derivedTypeMappings; } + + private static IReadOnlyCollection BuildExistingTargetContainedMappings( + MappingBuilderContext ctx, + IReadOnlyCollection configs, + bool duplicatedSourceTypesAllowed + ) + { + var derivedTypeMappingSourceTypes = new HashSet(SymbolEqualityComparer.Default); + 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); + Func isAssignableToTarget = ctx.Target is ITypeParameterSymbol targetTypeParameter + ? t => ctx.SymbolAccessor.DoesTypeSatisfyTypeParameterConstraints(targetTypeParameter, t, ctx.Target.NullableAnnotation) + : t => ctx.SymbolAccessor.HasImplicitConversion(t, ctx.Target); + + foreach (var config in configs) + { + // set types non-nullable as they can never be null when type-switching. + var sourceType = config.SourceType.NonNullable(); + if (!duplicatedSourceTypesAllowed && !derivedTypeMappingSourceTypes.Add(sourceType)) + { + ctx.ReportDiagnostic(DiagnosticDescriptors.DerivedSourceTypeDuplicated, sourceType); + continue; + } + + if (!isAssignableToSource(sourceType)) + { + ctx.ReportDiagnostic(DiagnosticDescriptors.DerivedSourceTypeIsNotAssignableToParameterType, sourceType, ctx.Source); + continue; + } + + var targetType = config.TargetType.NonNullable(); + if (!isAssignableToTarget(targetType)) + { + ctx.ReportDiagnostic(DiagnosticDescriptors.DerivedTargetTypeIsNotAssignableToReturnType, targetType, ctx.Target); + continue; + } + + var mapping = ctx.FindOrBuildExistingTargetMapping( + sourceType, + targetType, + MappingBuildingOptions.KeepUserSymbol | MappingBuildingOptions.MarkAsReusable | MappingBuildingOptions.ClearDerivedTypes + ); + if (mapping == null) + { + ctx.ReportDiagnostic(DiagnosticDescriptors.CouldNotCreateMapping, sourceType, targetType); + continue; + } + + derivedTypeMappings.Add(mapping); + } + + return derivedTypeMappings; + } } diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs index 64a82a1681..8984d00985 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs @@ -10,6 +10,7 @@ public class ExistingTargetMappingBuilder 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/DerivedExistingTargetTypeMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/DerivedExistingTargetTypeMapping.cs new file mode 100644 index 0000000000..ae07cac07f --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Mappings/DerivedExistingTargetTypeMapping.cs @@ -0,0 +1,83 @@ +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 if with instance checks over known types and performs the provided mapping for each type. +/// +public class DerivedExistingTargetTypeMapping : ExistingTargetMapping +{ + private const string SourceName = "source"; + private const string TargetName = "target"; + private const string GetTypeMethodName = nameof(GetType); + private readonly IReadOnlyCollection _existingTargetTypeMappings; + + public DerivedExistingTargetTypeMapping( + ITypeSymbol sourceType, + ITypeSymbol targetType, + IReadOnlyCollection existingTargetTypeMappings + ) + : base(sourceType, targetType) + { + _existingTargetTypeMappings = existingTargetTypeMappings; + } + + public override IEnumerable Build(TypeMappingBuildContext ctx, ExpressionSyntax target) + { + var sourceExpression = TupleExpression(CommaSeparatedList(Argument(ctx.Source), Argument(target))); + var sections = _existingTargetTypeMappings.Select(x => BuildSwitchSection(ctx, x)).Append(BuildDefaultSwitchSection(ctx, target)); + + yield return ctx.SyntaxFactory.SwitchStatement(sourceExpression, List(sections)).AddLeadingLineFeed(ctx.SyntaxFactory.Indentation); + } + + private SwitchSectionSyntax BuildSwitchSection(TypeMappingBuildContext ctx, IExistingTargetMapping mapping) + { + var (sectionCtx, sourceVariableName) = ctx.WithNewScopedSource(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 = List(mapping.Build(statementContext, target).Append(breakStatement)); + + return SwitchSection().WithLabels(SingletonList(caseLabel)).WithStatements(statements); + } + + private SwitchSectionSyntax BuildDefaultSwitchSection(TypeMappingBuildContext ctx, ExpressionSyntax target) + { + // default: + var defaultCaseLabel = DefaultSwitchLabel().AddLeadingLineFeed(ctx.SyntaxFactory.Indentation + 1); + + // throw new ArgumentException(msg, nameof(ctx.Source)), + var sourceType = Invocation(MemberAccess(ctx.Source, GetTypeMethodName)); + var targetType = Invocation(MemberAccess(target, GetTypeMethodName)); + var throwExpression = ThrowArgumentExpression( + InterpolatedString($"Cannot map {sourceType} to {targetType} as there is no known derived type mapping"), + ctx.Source + ) + .AddLeadingLineFeed(ctx.SyntaxFactory.Indentation + 2); + + var stat = new StatementSyntax[] { ExpressionStatement(throwExpression) }; + return SwitchSection().WithLabels(SingletonList(defaultCaseLabel)).WithStatements(List(stat)); + } +} diff --git a/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs b/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs index afadf1584d..670ead1729 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs @@ -63,6 +63,14 @@ SyntaxFactoryHelper syntaxFactory return (ctx, scopedSourceName); } + public (TypeMappingBuildContext Context, string SourceName) WithNewScopedSource(string sourceName) + { + var scopedNameBuilder = NameBuilder.NewScope(); + var scopedSourceName = scopedNameBuilder.New(sourceName); + var ctx = new TypeMappingBuildContext(IdentifierName(scopedSourceName), ReferenceHandler, scopedNameBuilder, SyntaxFactory); + return (ctx, scopedSourceName); + } + public (TypeMappingBuildContext Context, string SourceName) WithNewSource(string sourceName = DefaultSourceName) { var scopedSourceName = NameBuilder.New(sourceName); 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..8059bfab16 100644 --- a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Switch.cs +++ b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Switch.cs @@ -23,4 +23,21 @@ 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) + { + return SyntaxFactory.SwitchStatement( + default, + TrailingSpacedToken(SyntaxKind.SwitchKeyword), + Token(SyntaxKind.None), + governingExpression, + Token(SyntaxKind.None), + LeadingLineFeedToken(SyntaxKind.OpenBraceToken), + List(sections), + LeadingLineFeedToken(SyntaxKind.CloseBraceToken) + ); + } + + 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