Skip to content

Commit

Permalink
feat: inline methods consisting of a single local variable declaratio…
Browse files Browse the repository at this point in the history
…n expression
  • Loading branch information
Markus Hartmair committed Aug 3, 2024
1 parent 918a5d8 commit da50bf9
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 12 deletions.
2 changes: 1 addition & 1 deletion docs/docs/configuration/queryable-projections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public static partial class CarMapper
Mapperly tries to inline user-implemented mapping methods.
For this to work, user-implemented mapping methods need to satisfy certain limitations:

- Only expression-bodied methods can be inlined.
- Only expression-bodied methods or methods that consist of a single local variable declaration expression can be inlined.
- The body needs to follow the [expression tree limitations](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/expression-trees/#limitations).
- Nested MethodGroups cannot be inlined.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ public static class InlineExpressionMappingBuilder

var methodSyntax = methodSyntaxRef.GetSyntax();

var bodyExpression = methodSyntax switch
if (methodSyntax is not MethodDeclarationSyntax { ParameterList.Parameters: [var sourceParameter] } methodDeclaration)
{
MethodDeclarationSyntax { ExpressionBody: { } body, ParameterList.Parameters: [var sourceParameter1] } => body.Expression,
MethodDeclarationSyntax { Body.Statements: [ReturnStatementSyntax singleStatement] } => singleStatement.Expression,
_ => null
};
ctx.ReportDiagnostic(DiagnosticDescriptors.QueryableProjectionMappingCannotInline, mapping.Method);
return null;
}

var bodyExpression = TryGetBodyExpression(methodDeclaration);
if (bodyExpression == null)
{
ctx.ReportDiagnostic(DiagnosticDescriptors.QueryableProjectionMappingCannotInline, mapping.Method);
Expand All @@ -47,12 +48,32 @@ public static class InlineExpressionMappingBuilder
return null;
}

if (methodSyntax is not MethodDeclarationSyntax { ParameterList.Parameters: [var sourceParameter] })
return new UserImplementedInlinedExpressionMapping(mapping, sourceParameter, inlineRewriter.MappingInvocations, bodyExpression);
}

private static ExpressionSyntax? TryGetBodyExpression(MethodDeclarationSyntax methodDeclaration)
{
return methodDeclaration switch
{
ctx.ReportDiagnostic(DiagnosticDescriptors.QueryableProjectionMappingCannotInline, mapping.Method);
return null;
}
// => expression
{ ExpressionBody: { } body } => body.Expression,

return new UserImplementedInlinedExpressionMapping(mapping, sourceParameter, inlineRewriter.MappingInvocations, bodyExpression);
// { return expression; }
{ Body.Statements: [ReturnStatementSyntax singleStatement] } => singleStatement.Expression,

// { var dest = expression; return dest; }
{
Body.Statements: [
LocalDeclarationStatementSyntax
{
Declaration.Variables: [{ Initializer: { } variableInitializer } variableDeclarator]
},
ReturnStatementSyntax { Expression: IdentifierNameSyntax identifierName }
]
} when identifierName.Identifier.Value == variableDeclarator.Identifier.Value
=> variableInitializer.Value,

_ => null
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private D MapToD(C v)
}

[Fact]
public Task ClassToClassNonInlinedMethod()
public Task ClassToClassInlinedSingleDeclaration()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
Expand All @@ -63,6 +63,29 @@ private D MapToD(C v)
return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task ClassToClassNonInlinedMethod()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
private partial System.Linq.IQueryable<B> Map(System.Linq.IQueryable<A> source);

private D MapToD(C v)
{
var dest = new D();
dest.Value = v.Value + "-mapped";
return dest;
}
""",
"class A { public string StringValue { get; set; } public C NestedValue { get; set; } }",
"class B { public string StringValue { get; set; } public D NestedValue { get; set; } }",
"class C { public string Value { get; set; } }",
"class D { public string Value { get; set; } }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task ClassToClassUserImplementedOrdering()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//HintName: Mapper.g.cs
// <auto-generated />
#nullable enable
public partial class Mapper
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
private partial global::System.Linq.IQueryable<global::B> Map(global::System.Linq.IQueryable<global::A> source)
{
#nullable disable
return System.Linq.Queryable.Select(source, x => new global::B()
{
StringValue = x.StringValue,
NestedValue = new global::D { Value = x.NestedValue.Value + "-mapped" },
});
#nullable enable
}
}

0 comments on commit da50bf9

Please sign in to comment.