From cd933e748da1ef0de8b3d5d92a893ff5189f2b10 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sun, 9 Jul 2023 23:34:34 +0100 Subject: [PATCH] fix: add modifying nested struct diagnostic --- src/Riok.Mapperly/AnalyzerReleases.Shipped.md | 1 + .../ObjectMemberMappingBodyBuilder.cs | 22 ++++++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 9 ++++++ .../Mapping/ObjectPropertyTest.cs | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md index 965710dd25..93abfefded 100644 --- a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md +++ b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md @@ -105,3 +105,4 @@ RMG043 | Mapper | Warning | Enum fallback values are only supported for the RMG044 | Mapper | Warning | An ignored enum member can not be found on the source enum RMG045 | Mapper | Warning | An ignored enum member can not be found on the target enum RMG046 | Mapper | Error | The used C# language version is not supported by Mapperly, Mapperly requires at least C# 9.0 +RMG047 | Mapper | Error | Cannot map to member path due to modifying a temporary value diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs index 2b263da9ac..dbd1065a66 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs @@ -135,6 +135,28 @@ public static bool ValidateMappingSpecification( return false; } + // cannot assign to intermediate value type + // invalid mapping when second to last member is a property that returns a value type + if (targetMemberPath.Path.Count > 1) + { + var member = targetMemberPath.Path[targetMemberPath.Path.Count - 2]; + if (member is PropertyMember { Type: { IsValueType: true, IsRefLikeType: false } }) + { + ctx.BuilderContext.ReportDiagnostic( + DiagnosticDescriptors.CannotMapToTemporarySourceMember, + ctx.Mapping.SourceType, + sourceMemberPath.FullName, + sourceMemberPath.Member.Type, + ctx.Mapping.TargetType, + targetMemberPath.FullName, + targetMemberPath.Member.Type, + member.Name, + member.Type + ); + return false; + } + } + // a target member path part is init only var noInitOnlyPath = allowInitOnlyMember ? targetMemberPath.ObjectPath : targetMemberPath.Path; if (noInitOnlyPath.Any(p => p.IsInitOnly)) diff --git a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs index d28c7ef23e..95e1ce97f9 100644 --- a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs +++ b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs @@ -410,4 +410,13 @@ internal static class DiagnosticDescriptors DiagnosticSeverity.Error, true ); + + public static readonly DiagnosticDescriptor CannotMapToTemporarySourceMember = new DiagnosticDescriptor( + "RMG047", + "Cannot map to member path due to modifying a temporary value", + "Cannot map from member {0}.{1} of type {2} to member path {3}.{4} of type {5} because {6}.{7} is a value type, returning a temporary value", + DiagnosticCategories.Mapper, + DiagnosticSeverity.Error, + true + ); } diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyTest.cs index fbc7c3c235..a003f56a8a 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyTest.cs @@ -443,4 +443,34 @@ public void ShouldIgnoreStaticConstructorAndDiagnostic() .HaveDiagnostic(DiagnosticDescriptors.CouldNotCreateMapping) .HaveAssertedAllDiagnostics(); } + + [Fact] + public void ModifyingTemporaryStructShouldDiagnostic() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapProperty("StringValue", "NestedValue.StringValue")] + partial B Map(A src); + """, + "class A { public string StringValue { get; set; } }", + "class B { public C NestedValue { get; set; } }", + "struct C { public string StringValue { get; set; } }" + ); + + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowAllDiagnostics) + .Should() + .HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped) + .HaveDiagnostic( + DiagnosticDescriptors.CannotMapToTemporarySourceMember, + "Cannot map from member A.StringValue of type string to member path B.NestedValue.StringValue of type string because NestedValue.C is a value type, returning a temporary value" + ) + .HaveAssertedAllDiagnostics() + .HaveSingleMethodBody( + """ + var target = new global::B(); + return target;" + """ + ); + } }