mapperly icon indicating copy to clipboard operation
mapperly copied to clipboard

Full nameof feature behaves inconsistently and lacks clear documentation

Open denisbredikhin opened this issue 5 months ago • 5 comments

Dear Mapperly team,

I’ve encountered some confusing behavior with the full nameof feature when using the [MapProperty] attribute. According to the documentation, it should be possible to write something like:

[MapProperty(nameof(Car.Make.Id), nameof(CarDto.MakeId))]

Even though nameof(Car.Make.Id) is not valid in plain C# (it would just return "Id"), Mapperly seems to support this extended syntax.

However, the documentation mentions that in some cases, the @ symbol must be used, like this:

[MapProperty(nameof(@Car.Make.Id), nameof(CarDto.MakeId))]

Unfortunately, it’s not clear when the @ is required and when it’s not. Based on my testing, the behavior seems inconsistent:

  • This works fine without @:

    [MapProperty(nameof(Car.Manufacturer.Name), nameof(CarDto.ManufacturerName))]
    
  • But this fails with a compile-time error:

    [MapProperty(nameof(Car.Manufacturer.ManufacturerKind.Name), nameof(CarDto.ManufacturerKind))]
    

    Error:

    Specified member ManufacturerKind.Name on source type MapperlyTest.Car was not found.
    
  • Adding @ fixes it:

    [MapProperty(nameof(@Car.Manufacturer.ManufacturerKind.Name), nameof(CarDto.ManufacturerKind))]
    

It seems that the @ is required when the property path has three or more segments, but not when it has only two. This behavior is not documented and feels unintuitive.

Could you please either:

  1. Clarify and update the documentation to explain exactly when the @ is required, or
  2. Consider fixing this inconsistency so that the @ is not needed at all (or always required), for consistency and better developer experience.

Reported relevant diagnostics

  • RMG006

Environment:

  • Mapperly Version: 4.2.1
  • Nullable reference types: enabled
  • .NET Version: 8.0.18
  • Target Framework: net8.0
  • Compiler Version: 4.14.0-3.25279.5 (995f12b6)
  • C# Language Version: 12.0
  • IDE: Visual Studio v17.14.5
  • OS: Windows 11

denisbredikhin avatar Jul 25 '25 11:07 denisbredikhin

The @ prefix is required when specifying a property path (e.g. Make.Id) rather than a single property (e.g. Id). Without the @, the expression is interpreted using the default C# nameof behavior. By using @, you explicitly enable Mapperly’s full-nameof feature, which builds a property path based on the entire nameof expression.

To resolve the correct property path, Mapperly ignores the namespace and class name(s), but includes all subsequent segments.

Examples
Without full-nameof: nameof(MyNamespace.Car.Make.Id)"Id"
With full-nameof: nameof(@MyNamespace.Car.Make.Id)"Make.Id"

The documentation will be improved in https://github.com/riok/mapperly/pull/1894.
If you have suggestions for further clarification, please let me know, otherwise, I hope this resolves the confusion.

latonz avatar Jul 28 '25 05:07 latonz

@latonz thanks for improving documentation.

But if @ sign is always required for complex properties, why is the following mapping working correctly without @ sign?

[MapProperty(nameof(Car.Manufacturer.Name), nameof(CarDto.ManufacturerName))]

nameof(Car.Manufacturer.Name) -> "Name"

But the generate code is:

if (car.Manufacturer != null)
{
    target.ManufacturerName = car.Manufacturer.Name;
}

denisbredikhin avatar Jul 28 '25 07:07 denisbredikhin

Isn't there a warning reported that Name couldn't be found? It still works due to Mapperly's auto-flattening.

latonz avatar Jul 28 '25 07:07 latonz

It still works due to Mapperly's auto-flattening.

That was my first idea, but such mapping also works:

[MapProperty(nameof(Car.YYY.Bzzz), nameof(CarDto.RamambuHaraMambaRo))]
public static partial CarDto MapCarToDto(Car car);

The generated code is correct:

if (car.YYY != null)
{
    target.RamambuHaraMambaRo = car.YYY.Bzzz;
}

I was thinking that maybe Mapperly is looking for the property Bzzz across the full hierarchy, and added the two properties with the same type to "fool" this search:

public class Car
{
    public Manufacturer? YYY { get; set; }

    public Manufacturer? OneMoreManufacturer { get; set; }
}

But the generated code is still correct...

denisbredikhin avatar Jul 28 '25 08:07 denisbredikhin

You are absolutely right, and this is indeed a bug. I'll look into how we can fix it without causing significant disruptions to the existing code.

latonz avatar Jul 30 '25 14:07 latonz

@latonz thanks for solving this!

denisbredikhin avatar Dec 22 '25 16:12 denisbredikhin