From 2004686736d2e9e143ecf1f5ea36906432118e56 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 | 21 +++++++++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 9 ++++++++ .../Mapping/ObjectPropertyTest.cs | 23 +++++++++++++++++++ 4 files changed, 54 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 90df540cc0..0c177f6d4b 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs @@ -135,6 +135,27 @@ public static bool ValidateMappingSpecification( return false; } + // cannot assign to intermediate value type + if (targetMemberPath.Path.Count > 1) + { + var member = targetMemberPath.Path.Reverse().Skip(1).First(); + if (member.Type is { 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 true; + } + } + // 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..022cdc9eaf 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyTest.cs @@ -443,4 +443,27 @@ 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.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(); + } }