riok / mapperly

A .NET source generator for generating object mappings. No runtime reflection.
https://mapperly.riok.app
Apache License 2.0
2.53k stars 138 forks source link

Property with private or internal setter are not mapped #1458

Open cs-clarence opened 3 weeks ago

cs-clarence commented 3 weeks ago

Describe the bug Pubic property with private setter are not mapped even with IncludedMembers = MemberVisibility.All applied

Declaration code https://dotnetfiddle.net/E4iGqY - reproduction

using System;
using Riok.Mapperly.Abstractions;

public class User(string emailAddress) 
{
    public string EmailAddress { get; private set; } = emailAddress;
}

public record UserUpdateDto(string EmailAddress);

[Mapper(IncludedMembers = MemberVisibility.All)]
public static partial class UserMapper 
{
    static partial void ApplyTo(this UserUpdateDto input, User user);

    public static void ApplyUpdates(this UserUpdateDto input, User user) 
    {
        UserMapper.ApplyTo(input, user);
    }
}

public class Program
{
    public static void Main()
    {
        var user = new User("user@gmail.com");
        var updates = new UserUpdateDto("new-user@gmail.com");

        updates.ApplyUpdates(user);

        Console.WriteLine(user.EmailAddress); // still shows user@gmail.com
    }
}

Environment (please complete the following information):

latonz commented 3 weeks ago

I cannot reproduce this locally and I'm not sure if .NET fiddle runs source generators correctly. The following works as expected for me:

# in a new empty directory
dotnet new console
dotnet add package Riok.Mapperly -v 3.6.0
# replace content of Program.cs with provided content
dotnet run
# prints new-user@gmail.com as expected...

Do these steps work for you? Can you reproduce the issue in Rider? in VS Code? in VS? on the terminal?

cs-clarence commented 2 weeks ago

I cannot reproduce this locally and I'm not sure if .NET fiddle runs source generators correctly. The following works as expected for me:

# in a new empty directory
dotnet new console
dotnet add package Riok.Mapperly -v 3.6.0
# replace content of Program.cs with provided content
dotnet run
# prints new-user@gmail.com as expected...

Do these steps work for you? Can you reproduce the issue in Rider? in VS Code? in VS? on the terminal?

Can confirm this worked, however the minimal reproduction is not complete, my bad. Here is a better one.

I experienced it when the object mapped is in a different project.

The generated code in Rider is:

// <auto-generated />
#nullable enable
public static partial class UserMapper
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.6.0.0")]
    static partial void ApplyTo(this global::UserUpdateDto input, global::User user)
    {
        user.SetEmailAddress(input.EmailAddress);
    }
}

[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.6.0.0")]
static file class UnsafeAccessor
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.6.0.0")]
    [global::System.Runtime.CompilerServices.UnsafeAccessor(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Method, Name = "set_EmailAddress")]
    public static extern void SetEmailAddress(this global::User target, string value);
}

The generated code seems correct and is the exact same as when the class is contained in the same project. However, when running it, the changes are not applied.

cc @latonz

latonz commented 2 weeks ago

Thanks for the update, I can reproduce it now. It seems that PropertySymbol.IsReadOnly is true and PropertySymbol.SetMethod is null. As soon as I find the time I'll investigate further why exactly this is the case... Probably private members are just not visible to other compilation units? It may work in the IDE since these are related compilation units in the IDE.