Describe the bug
In a queryable projection (ProjectToXxx(this IQueryable<T>)), when Mapperly inlines a user-mapping helper referenced via Use = nameof(Helper) (through [MapPropertyFromSource] or [MapProperty]) whose parameter type is a supertype (base class or interface) of the projection's source element type, the generated expression upcasts the substituted source parameter with the wrong operator precedence.
It emits (global::IBase)x.Value instead of ((global::IBase)x).Value, so the cast binds to the member-access result rather than to x. The generated projection then fails to compile:
error CS0030: Cannot convert type 'int?' to 'IBase'
The instance Map path for the same mapping is generated correctly — it casts the whole source (MapResult((global::IBase)source)). Only the queryable-projection inlining mis-places the cast.
To Reproduce
using System.Linq;
using Riok.Mapperly.Abstractions;
public interface IBase { int? Value { get; } }
public class Source : IBase { public int Id { get; set; } public int? Value { get; set; } }
public class Target { public int Id { get; set; } public int? Result { get; set; } }
[Mapper(AutoUserMappings = false)]
public static partial class MyMapper
{
public static partial IQueryable<Target> ProjectToTarget(this IQueryable<Source> q);
[MapPropertyFromSource(nameof(Target.Result), Use = nameof(MapResult))]
private static partial Target Map(Source source);
// Parameter type IBase is a supertype of the projection's source element type (Source).
private static int? MapResult(IBase source) => source.Value;
}
Generated code
// projection — invalid, the (IBase) cast binds to the member access:
Result = (global::IBase)x.Value,
// instance Map path — correct:
target.Result = MapResult((global::IBase)source);
Expected behavior
The projection should parenthesize the cast operand:
Result = ((global::IBase)x).Value,
or, since Source : IBase, omit the (unnecessary) upcast entirely:
Mapperly version
5.0.0-next.8 (also reproduces on 5.0.0-next.7).
Additional context
The same defect surfaces when sharing configuration between mappings of different concrete source types via [IncludeMappingConfiguration] on a base mapping whose source is an interface — any Use= helper taking the interface-typed whole record then hits this in the projection path.
Describe the bug
In a queryable projection (
ProjectToXxx(this IQueryable<T>)), when Mapperly inlines a user-mapping helper referenced viaUse = nameof(Helper)(through[MapPropertyFromSource]or[MapProperty]) whose parameter type is a supertype (base class or interface) of the projection's source element type, the generated expression upcasts the substituted source parameter with the wrong operator precedence.It emits
(global::IBase)x.Valueinstead of((global::IBase)x).Value, so the cast binds to the member-access result rather than tox. The generated projection then fails to compile:The instance
Mappath for the same mapping is generated correctly — it casts the whole source (MapResult((global::IBase)source)). Only the queryable-projection inlining mis-places the cast.To Reproduce
Generated code
Expected behavior
The projection should parenthesize the cast operand:
or, since
Source : IBase, omit the (unnecessary) upcast entirely:Mapperly version
5.0.0-next.8(also reproduces on5.0.0-next.7).Additional context
The same defect surfaces when sharing configuration between mappings of different concrete source types via
[IncludeMappingConfiguration]on a base mapping whose source is an interface — anyUse=helper taking the interface-typed whole record then hits this in the projection path.