From 545b1da626df02d75efdd123ca0aa534bf023919 Mon Sep 17 00:00:00 2001 From: latonz Date: Thu, 24 Oct 2024 23:40:16 +0200 Subject: [PATCH] Fix inconsistent handling of mixed nullable contexts Allow using non-null returning mapping methods when target type does allow nulls --- .../Descriptors/MappingBuilderContext.cs | 4 ++- .../MappingBuilders/MappingBuilder.cs | 4 +++ .../Descriptors/MappingCollection.cs | 6 ++-- .../Descriptors/TypeMappingKey.cs | 2 ++ .../Mapping/ObjectPropertyNullableTest.cs | 29 +++++++++++++++ ...erivedTypesShouldWork#Mapper.g.verified.cs | 36 +++++++++++++++++++ 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/Riok.Mapperly.Tests/_snapshots/ObjectPropertyNullableTest.MixedNullableContextsWithDerivedTypesShouldWork#Mapper.g.verified.cs diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs index a0d0fc1ae3..9b5911227f 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs @@ -154,7 +154,9 @@ bool ignoreDerivedTypes Location? diagnosticLocation = null ) { - return FindMapping(mappingKey) ?? BuildMapping(mappingKey, options, diagnosticLocation); + return FindMapping(mappingKey) + ?? FindMapping(mappingKey.TargetNonNullable()) + ?? BuildMapping(mappingKey, options, diagnosticLocation); } /// diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs index 1045ccc1d8..3a9ffc45fe 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs @@ -68,6 +68,10 @@ public class MappingBuilder(MappingCollection mappings, MapperDeclaration mapper { mappings.AddNewInstanceMapping(mapping, ctx.MappingKey.Configuration); } + else if (mapping is MethodMapping methodMapping) + { + mappings.AddMethodMapping(methodMapping); + } mappings.EnqueueToBuildBody(mapping, ctx); return mapping; diff --git a/src/Riok.Mapperly/Descriptors/MappingCollection.cs b/src/Riok.Mapperly/Descriptors/MappingCollection.cs index 05cb0a2f26..610f6b55a9 100644 --- a/src/Riok.Mapperly/Descriptors/MappingCollection.cs +++ b/src/Riok.Mapperly/Descriptors/MappingCollection.cs @@ -98,7 +98,7 @@ public MappingCollectionAddResult AddNewInstanceMapping(INewInstanceMapping mapp { if (mapping is MethodMapping methodMapping) { - _methodMappings.Add(methodMapping); + AddMethodMapping(methodMapping); } return _newInstanceMappings.TryAddAsDefault(mapping, config); @@ -108,12 +108,14 @@ public MappingCollectionAddResult AddExistingTargetMapping(IExistingTargetMappin { if (mapping is MethodMapping methodMapping) { - _methodMappings.Add(methodMapping); + AddMethodMapping(methodMapping); } return _existingTargetMappings.TryAddAsDefault(mapping, config); } + public void AddMethodMapping(MethodMapping methodMapping) => _methodMappings.Add(methodMapping); + public void AddNamedNewInstanceUserMappings(string name, IEnumerable mappings) { foreach (var mapping in mappings) diff --git a/src/Riok.Mapperly/Descriptors/TypeMappingKey.cs b/src/Riok.Mapperly/Descriptors/TypeMappingKey.cs index b0062da3c8..c499bf2e4a 100644 --- a/src/Riok.Mapperly/Descriptors/TypeMappingKey.cs +++ b/src/Riok.Mapperly/Descriptors/TypeMappingKey.cs @@ -26,6 +26,8 @@ public TypeMappingKey(ITypeMapping mapping, TypeMappingConfiguration? config = n public TypeMappingKey NonNullable() => new(Source.NonNullable(), Target.NonNullable(), Configuration); + public TypeMappingKey TargetNonNullable() => new(Source, Target.NonNullable(), Configuration); + public override bool Equals(object? obj) => obj is TypeMappingKey other && _comparer.Equals(Source, other.Source) diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs index 742260b7a1..7fd5853a59 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs @@ -899,4 +899,33 @@ private A(C value) """ ); } + + [Fact] + public Task MixedNullableContextsWithDerivedTypesShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapDerivedType] + public partial BBase Map(ABase src); + """, + """ + #nullable disable + public abstract record BBase + { + public List Objects { get; init; } = []; + } + + public record B : BBase; + + #nullable enable + public abstract record ABase + { + public List Objects { get; init; } = []; + } + + public record A: ABase; + """ + ); + return TestHelper.VerifyGenerator(source); + } } diff --git a/test/Riok.Mapperly.Tests/_snapshots/ObjectPropertyNullableTest.MixedNullableContextsWithDerivedTypesShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/ObjectPropertyNullableTest.MixedNullableContextsWithDerivedTypesShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..0cdab5cb2c --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/ObjectPropertyNullableTest.MixedNullableContextsWithDerivedTypesShouldWork#Mapper.g.verified.cs @@ -0,0 +1,36 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] + public partial global::BBase Map(global::ABase src) + { + return src switch + { + global::A x => MapToB(x), + _ => throw new System.ArgumentException($"Cannot map {src.GetType()} to BBase as there is no known derived type mapping", nameof(src)), + }; + } + + [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] + private global::B MapToB(global::A source) + { + var target = new global::B() + { + Objects = MapToListOfBBase(source.Objects), + }; + return target; + } + + [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] + private global::System.Collections.Generic.List MapToListOfBBase(global::System.Collections.Generic.IReadOnlyCollection source) + { + var target = new global::System.Collections.Generic.List(source.Count); + foreach (var item in source) + { + target.Add(Map(item)); + } + return target; + } +} \ No newline at end of file