diff --git a/src/Riok.Mapperly/Helpers/NullableSymbolExtensions.cs b/src/Riok.Mapperly/Helpers/NullableSymbolExtensions.cs index 8a65d315c1..d0ecf9d452 100644 --- a/src/Riok.Mapperly/Helpers/NullableSymbolExtensions.cs +++ b/src/Riok.Mapperly/Helpers/NullableSymbolExtensions.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -38,7 +39,7 @@ internal static bool TryUpgradeNullable(this ITypeSymbol symbol, [NotNullWhen(tr return false; } - upgradedSymbol = symbol.WithNullableAnnotation(NullableAnnotation.Annotated); + upgradedSymbol = symbol.WithDeepNullableAnnotation(NullableAnnotation.Annotated); return true; } @@ -112,4 +113,24 @@ internal static bool IsNullable(this ITypeParameterSymbol typeParameter, Nullabl internal static bool IsNullable(this NullableAnnotation nullable) => nullable is NullableAnnotation.Annotated or NullableAnnotation.None; + + /// + /// Returns a new type symbol with the provided nullable annotation. + /// Also sets the nullable annotation on all type arguments of the . + /// + /// The symbol to work on. + /// The to set. + /// The new symbol with the given nullable annotation. + private static ITypeSymbol WithDeepNullableAnnotation(this ITypeSymbol symbol, NullableAnnotation annotation) + { + if (symbol is INamedTypeSymbol { TypeArguments.Length: > 0 } namedSymbol) + { + symbol = namedSymbol.ConstructedFrom.Construct( + namedSymbol.TypeArguments, + Enumerable.Repeat(annotation, namedSymbol.TypeArguments.Length).ToImmutableArray() + ); + } + + return symbol.WithNullableAnnotation(annotation); + } } diff --git a/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs b/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs index d6f2641e1b..73abe94557 100644 --- a/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs @@ -550,6 +550,14 @@ public Task ArrayToReadOnlyCollectionShouldUpgradeNullability() return TestHelper.VerifyGenerator(source, TestHelperOptions.DisabledNullable); } + [Fact] + public Task ShouldUpgradeNullabilityOfGenericInDisabledNullableContext() + { + var source = TestSourceBuilder.Mapping("IList", "IList", "record A(int V);", "record B(int V);"); + + return TestHelper.VerifyGenerator(source, TestHelperOptions.DisabledNullable); + } + [Fact] public Task ArrayToCollectionShouldUpgradeNullability() { diff --git a/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.ShouldUpgradeNullabilityOfGenericInDisabledNullableContext#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.ShouldUpgradeNullabilityOfGenericInDisabledNullableContext#Mapper.g.verified.cs new file mode 100644 index 0000000000..7bde9f2820 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.ShouldUpgradeNullabilityOfGenericInDisabledNullableContext#Mapper.g.verified.cs @@ -0,0 +1,25 @@ +//HintName: Mapper.g.cs +// +#nullable enable +public partial class Mapper +{ + private partial global::System.Collections.Generic.IList? Map(global::System.Collections.Generic.IList? source) + { + if (source == null) + return default; + var target = new global::System.Collections.Generic.List(source.Count); + foreach (var item in source) + { + target.Add(MapToB(item)); + } + return target; + } + + private global::B? MapToB(global::A? source) + { + if (source == null) + return default; + var target = new global::B(source.V); + return target; + } +} \ No newline at end of file