Incorrect nullable handling with implicit operators
Describe the bug When mapping from a nullable type (either struct or class) to a type that has an implicit operator, mapperly incorrectly generates null handling checks. This occurs both when mapping directly, as in the example below, or when mapping a class with properties of this type.
As the implicit operator can take a nullable type, I would expect the nullable value to be passed in without the null check.
In cases where the implicit operator can't take a nullable type (e.g., int? to TrackedProperty
Declaration code
using Riok.Mapperly.Abstractions;
[Mapper]
public partial class Simple1
{
public static partial TrackedProperty<int?> ToFoo(int? source);
}
[Mapper]
public partial class Simple2
{
public static partial Dst ToFoo(Src source);
}
public class Src
{
public int? Foo { get; set; }
}
public class Dst
{
public TrackedProperty<int?> Foo { get; set; }
}
public readonly record struct TrackedProperty<T>
{
private readonly T value;
public TrackedProperty(T value)
{
this.value = value;
}
public static implicit operator TrackedProperty<T>(T value)
{
return new TrackedProperty<T>(value);
}
}
Actual relevant generated code
// <auto-generated />
#nullable enable
public partial class Simple1
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.0.0.0")]
public static partial global::TrackedProperty<int?> ToFoo(int? source)
{
return source == null ? throw new System.ArgumentNullException(nameof(source)) : (global::TrackedProperty<int?>)source.Value;
}
}
public partial class Simple2
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.0.0.0")]
public static partial global::Dst ToFoo(global::Src source)
{
var target = new global::Dst();
if (source.Foo != null)
{
target.Foo = (global::TrackedProperty<int?>)source.Foo.Value;
}
return target;
}
}
Expected relevant generated code
// <auto-generated />
#nullable enable
public partial class Simple1
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.0.0.0")]
public static partial global::TrackedProperty<int?> ToFoo(int? source)
{
return (global::TrackedProperty<int?>)source;
}
}
public partial class Simple2
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.0.0.0")]
public static partial global::Dst ToFoo(global::Src source)
{
var target = new global::Dst();
target.Foo = (global::TrackedProperty<int?>)source.Foo;
return target;
}
}
Environment (please complete the following information):
- Mapperly Version: 3.6.0/4.0.0-next.3
- Nullable reference types: enabled
- .NET Version: .net framework 4.8/.net 8
- Target Framework: .net standard 2.0/.net framework 4.8/.net 8
- Compiler Version: 4.11.0-3.24365.8 (9e9c7c1d)
- C# Language Version: 12
- IDE: Visual Studio v17.10.5/Rider 2024.2.4
- OS: Windows 11
Additional context We have a partial workaround by declaring a user-implemented mapping, however we seem to be unable to create a generic user-implemented mapper.
// This does not work
public static TrackedProperty<T> UserMap<T>(T source) => source;
// This does, but requires a separate function per type
public static TrackedProperty<int?> UserMap(int? source) => source;
Is this issue in progress? If not I can handle with that
Not yet in progress, feel free to contribute.
I have just analysed this: It doesn't seem to be related to nullability, but to generics. Mapperly does not support open generic mappings yet. Closing in favour of https://github.com/riok/mapperly/issues/1285.