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 in the example below), the current behaviour of generating the null check is correct.
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;
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 in the example below), the current behaviour of generating the null check is correct.
Declaration code
Actual relevant generated code
Expected relevant generated code
Environment (please complete the following information):
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.