Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a886999
feat: add ParameterScope and TypeMappingBuildContext.AdditionalParame…
Demivan Mar 18, 2026
34ce1bd
feat: forward additional parameters in MethodMapping.Build() and subc…
Demivan Mar 18, 2026
a8b638c
feat: integrate ParameterScope into MembersMappingState and MappingBu…
Demivan Mar 18, 2026
8642f58
feat: support additional parameters in MapValue Use methods
Demivan Mar 18, 2026
0e08bdf
feat: prefer user-defined nested mappings with matching additional pa…
Demivan Mar 18, 2026
9970a90
feat: forward additional parameters through UseNamedMappingBuilder
Demivan Mar 18, 2026
6564a95
feat: support additional parameters in queryable projection inlining
Demivan Mar 18, 2026
ca57b9a
feat: support additional parameters with MappingTarget existing targe…
Demivan Mar 18, 2026
5d1f751
test: add edge case tests for additional parameter forwarding
Demivan Mar 18, 2026
3773229
refactor: use ParameterScope as single source of truth for additional…
Demivan Mar 18, 2026
a49de46
test: add tests for additional parameter forwarding
Demivan Mar 18, 2026
b1a7012
docs: document additional parameter forwarding feature
Demivan Mar 18, 2026
fa7dab7
feat: projection config discovery matches element mappings by additio…
Demivan Mar 18, 2026
9271176
docs: document projection property configuration with additional para…
Demivan Mar 18, 2026
d096032
refactor: remove dead allowAdditionalParameters parameter from BuildP…
Demivan Mar 18, 2026
34a6262
refactor: consolidate ParameterScope.MarkUsed into batch overloads
Demivan Mar 20, 2026
358b9a9
refactor: make ParameterScope non-nullable with Empty singleton default
Demivan Mar 21, 2026
f5618be
refactor: unify ParameterScope matching API with CanMatchParameters a…
Demivan Mar 21, 2026
8927fa8
refactor: merge FindUserMappingWithParameters into Find with optional…
Demivan Mar 22, 2026
e915ac6
refactor: move ParameterScope.MarkUsed into Find and remove FindParam…
Demivan Mar 22, 2026
5bbcba7
refactor: index user mappings by type pair with ListDictionary for ef…
Demivan Mar 22, 2026
c829a0a
refactor: eliminate duplicate GetAllDirectlyAccessibleMethods query i…
Demivan Mar 22, 2026
f9fa755
refactor: rename EmptyParameters to _emptyParameters per naming conve…
Demivan Apr 9, 2026
d0b1a73
refactor: use Skip(1).Any() instead of Count() > 1 to avoid full enum…
Demivan Apr 9, 2026
c5006a4
refactor: rename test to describe expected state without implying change
Demivan Apr 9, 2026
91b20cf
refactor: make CanMatchParameters(IReadOnlyCollection) private
Demivan Apr 9, 2026
ddbfc6f
refactor: centralize TrimStart('@') into MethodParameter.NormalizeNam…
Demivan Apr 9, 2026
274e463
docs: split queryable projections example into tabs and fix formatting
Demivan Apr 9, 2026
5f8de3d
refactor: optimize inline expression parameter lookup with ContainsKe…
Demivan Apr 9, 2026
445b165
feat: add RMG098 diagnostic for case-insensitive duplicate additional…
Demivan Apr 9, 2026
fd3abff
test: add integration test for additional parameter inlining in proje…
Demivan Apr 9, 2026
69dbdde
refactor: optimize Linq Count call
Demivan Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 139 additions & 5 deletions docs/docs/configuration/additional-mapping-parameters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ An additional mapping parameter has lower priority than a `MapProperty` mapping,
but higher than a by-name matched regular member mapping.

<Tabs>
<TabItem default label="Declaration" value="declaration">
<TabItem value="declaration" label="Declaration" default>
```csharp
[Mapper]
public partial class CarMapper
Expand All @@ -39,7 +39,7 @@ but higher than a by-name matched regular member mapping.
```

</TabItem>
<TabItem default label="Generated code" value="generated">
<TabItem value="generated" label="Generated code">
```csharp
[Mapper]
public partial class CarMapper
Expand All @@ -55,7 +55,141 @@ but higher than a by-name matched regular member mapping.
target.Name = name;
// highlight-end
return target;
}
}
}
```
</TabItem>
</Tabs>

## Forwarding parameters to nested mappings

Additional parameters can be forwarded to user-defined nested mapping methods.
When Mapperly resolves a nested type mapping (e.g., `A.Nested → B.Nested`),
it will prefer a user-defined mapping method whose additional parameters can be satisfied
from the parent mapping's additional parameters, matched by name.

Mapperly will **not** auto-generate mapping methods with additional parameters.
Only explicitly declared user-defined methods are considered.

<Tabs>
<TabItem value="nested-declaration" label="Declaration" default>
```csharp
[Mapper]
public partial class OrderMapper
{
public partial OrderDto Map(Order source, int currentUserId);
// highlight-start
// Mapperly will call MapItem with the currentUserId parameter forwarded
private partial OrderItemDto MapItem(OrderItem source, int currentUserId);
// highlight-end
}
```
</TabItem>
<TabItem value="nested-generated" label="Generated code">
```csharp
[Mapper]
public partial class OrderMapper
{
public partial OrderDto Map(Order source, int currentUserId)
{
var target = new OrderDto();
// highlight-start
target.Item = MapItem(source.Item, currentUserId);
// highlight-end
return target;
}
}
```
</TabItem>
</Tabs>

## Forwarding parameters to Use methods

Additional parameters can be passed to methods referenced by `MapValue(Use = ...)`.
The method's parameters are matched by name from the mapping's additional parameters.

<Tabs>
<TabItem value="use-declaration" label="Declaration" default>
```csharp
[Mapper]
public partial class OrderMapper
{
// highlight-start
[MapValue(nameof(OrderDto.Total), Use = nameof(ComputeTotal))]
// highlight-end
public partial OrderDto Map(Order source, decimal taxRate);

// highlight-start
private decimal ComputeTotal(decimal taxRate) => 100m * (1 + taxRate);
// highlight-end
}
```

</TabItem>
<TabItem value="use-generated" label="Generated code">
```csharp
[Mapper]
public partial class OrderMapper
{
public partial OrderDto Map(Order source, decimal taxRate)
{
var target = new OrderDto();
// highlight-start
target.Total = ComputeTotal(taxRate);
// highlight-end
return target;
}
}
```
</TabItem>
</Tabs>

Additional parameters can also be forwarded to methods referenced by `MapProperty(Use = ...)`.
The first parameter of the `Use` method receives the source member value;
the remaining parameters are matched by name from the mapping's additional parameters.
This works with both mapper-internal methods and [external mapper](./external-mappings.mdx) methods.
See also [user-implemented property mappings](./mapper.mdx#user-implemented-property-mappings).

## Additional parameters in queryable projections

Additional parameters work in queryable projections.
The parameters become captured variables in the generated projection lambda.
User-implemented methods with additional parameters are inlined following the same rules
as regular [user-implemented mapping methods in projections](./queryable-projections.mdx#user-implemented-mapping-methods).

<Tabs>
<TabItem value="projection-declaration" label="Declaration" default>
```csharp
[Mapper]
public static partial class OrderMapper
{
public static partial IQueryable<OrderDto> ProjectToDto(
this IQueryable<Order> q,
// highlight-next-line
int currentUserId
);
}
```
</TabItem>
<TabItem value="projection-generated" label="Generated code">
```csharp
[Mapper]
public static partial class OrderMapper
{
public static partial IQueryable<OrderDto> ProjectToDto(
this IQueryable<Order> q,
// highlight-next-line
int currentUserId
)
{
return q.Select(x => new OrderDto()
{
// ...
// highlight-start
CurrentUserId = currentUserId, // captured from outer scope
// highlight-end
});
}
}
```
</TabItem>
Expand All @@ -64,10 +198,10 @@ but higher than a by-name matched regular member mapping.
:::info
Mappings with additional parameters do have some limitations:

- The additional parameters are not passed to nested mappings.
- Additional parameters are forwarded to user-defined nested mapping methods only (not auto-generated ones).
- A mapping with additional mapping parameters cannot be the default mapping
(it is not used by Mapperly when encountering a nested mapping for the given types),
see also [default mapping methods](./user-implemented-methods.mdx##default-mapping-methods).
see also [default mapping methods](./user-implemented-methods.mdx#default-mapping-methods).
- Generic and runtime target type mappings do not support additional type parameters.
- Derived type mappings do not support additional type parameters.
:::
46 changes: 46 additions & 0 deletions docs/docs/configuration/analyzer-diagnostics/RMG097.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
sidebar_label: RMG097
description: 'Mapperly analyzer diagnostic RMG097 — MapValue Use method parameters cannot be satisfied'
---

# RMG097 — MapValue Use method parameters cannot be satisfied

A method referenced by `MapValue(Use = ...)` has parameters that cannot be matched
from the mapping method's [additional parameters](../additional-mapping-parameters.mdx).

To fix this, ensure the mapping method declares additional parameters with names matching
the parameters of the `Use` method.

## Example

`GetValue` requires an `int ctx` parameter,
but the `Map` method does not declare a matching additional parameter.

```csharp
[Mapper]
public partial class MyMapper
{
// highlight-start
// Error: GetValue requires 'ctx' but Map has no additional parameters
[MapValue(nameof(B.Value), Use = nameof(GetValue))]
// highlight-end
public partial B Map(A source);

private int GetValue(int ctx) => ctx * 2;
}
```

Add the missing parameter to the mapping method:

```csharp
[Mapper]
public partial class MyMapper
{
[MapValue(nameof(B.Value), Use = nameof(GetValue))]
// highlight-start
public partial B Map(A source, int ctx);
// highlight-end

private int GetValue(int ctx) => ctx * 2;
}
```
49 changes: 49 additions & 0 deletions docs/docs/configuration/analyzer-diagnostics/RMG098.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
sidebar_label: RMG098
description: 'Mapperly analyzer diagnostic RMG098 — Named mapping additional parameters cannot be satisfied'
---

# RMG098 — Named mapping additional parameters cannot be satisfied

A named mapping referenced by `MapProperty(Use = ...)` or `MapPropertyFromSource(Use = ...)`
has [additional parameters](../additional-mapping-parameters.mdx) that cannot be matched
from the caller's additional parameters.

To fix this, ensure the calling mapping method declares additional parameters with names matching
the parameters of the referenced named mapping.

## Example

`Transform` requires an `int multiplier` parameter,
but the `Map` method does not declare a matching additional parameter.

```csharp
[Mapper]
public partial class MyMapper
{
// highlight-start
// Error: Transform requires 'multiplier' but Map has no additional parameters
[MapProperty(nameof(A.Value), nameof(B.Result), Use = nameof(Transform))]
// highlight-end
public partial B Map(A source);

[UserMapping(Default = false)]
private partial int Transform(int value, int multiplier);
}
```

Add the missing parameter to the calling mapping method:

```csharp
[Mapper]
public partial class MyMapper
{
[MapProperty(nameof(A.Value), nameof(B.Result), Use = nameof(Transform))]
// highlight-start
public partial B Map(A source, int multiplier);
// highlight-end

[UserMapping(Default = false)]
private partial int Transform(int value, int multiplier);
}
```
36 changes: 36 additions & 0 deletions docs/docs/configuration/analyzer-diagnostics/RMG099.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
sidebar_label: RMG099
description: 'Mapperly analyzer diagnostic RMG099 — Duplicate additional parameter names differing only in casing'
---

# RMG099 — Duplicate additional parameter names differing only in casing

A mapping method has multiple [additional parameters](../additional-mapping-parameters.mdx)
whose names differ only in casing (e.g., `userId` and `UserId`).
Mapperly matches additional parameters case-insensitively,
so only the first parameter is used and the others are ignored.

## Example

```csharp
[Mapper]
public partial class MyMapper
{
// highlight-start
// Error: UserId and userId differ only in casing
public partial B Map(A source, int UserId, int userId);
// highlight-end
}
```

Rename the parameters to have distinct names or delete duplicate ones:

```csharp
[Mapper]
public partial class MyMapper
{
// highlight-start
public partial B Map(A source, int userId);
// highlight-end
}
```
23 changes: 23 additions & 0 deletions docs/docs/configuration/constant-generated-values.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ Make sure the return type exactly matches the target type.

This also works for constructor parameters.

### Using additional mapping parameters

When the mapping method has [additional parameters](./additional-mapping-parameters.mdx),
they can be passed to `MapValue(Use = ...)` methods.
The method's parameters are matched by name from the mapping's additional parameters:

<Tabs>
<TabItem value="declaration" label="Declaration" default>
```csharp
[MapValue(nameof(CarDto.FormattedDate), Use = nameof(FormatDate))]
public partial CarDto Map(Car car, string dateFormat);

string FormatDate(string dateFormat) => DateTime.Now.ToString(dateFormat);
```
</TabItem>
<TabItem value="generated" label="Generated code" default>
```csharp
target.FormattedDate = FormatDate(dateFormat);
```
</TabItem>

</Tabs>

### Named mapping

The name of the value generator can be overridden by the `NamedMapping` attribute:
Expand Down
29 changes: 29 additions & 0 deletions docs/docs/configuration/mapper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,35 @@ public partial class CarMapper
}
```

#### Use methods with additional parameters

When the mapping method has [additional parameters](./additional-mapping-parameters.mdx),
they can be forwarded to `Use` methods.
For `MapProperty(Use = ...)`, the first parameter of the `Use` method receives the source member value,
and the remaining parameters are matched by name from the mapping's additional parameters:

```csharp
[Mapper]
public partial class CarMapper
{
// highlight-start
[MapProperty(nameof(Car.Price), nameof(CarDto.DisplayPrice), Use = nameof(FormatPrice))]
// highlight-end
public partial CarDto MapCar(Car source, string currency);

// highlight-start
[UserMapping(Default = false)]
private string FormatPrice(decimal price, string currency)
=> $"{currency} {price:F2}";
// highlight-end

// generates
target.DisplayPrice = FormatPrice(source.Price, currency);
}
```

See also [additional mapping parameters](./additional-mapping-parameters.mdx) for more details on parameter forwarding.

#### Custom mapping names

By default, Mapperly uses the method name as the identifier for each mapping.
Expand Down
Loading
Loading