Skip to content

Commit

Permalink
feat: Implement custom source formatting to improve performance (#706)
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz committed Sep 1, 2023
1 parent 23c2adb commit 44b5df9
Show file tree
Hide file tree
Showing 156 changed files with 1,544 additions and 3,282 deletions.
2 changes: 1 addition & 1 deletion docs/docs/configuration/mapper.mdx
Expand Up @@ -184,7 +184,7 @@ dotnet_diagnostic.RMG020.severity = error # Unmapped source member

### Strict enum mappings

To enforce strict enum mappings set `RMG037` and `RMG038` to error, see [strict enum mappings](./enum.mdx).
To enforce strict enum mappings set `RMG037` and `RMG038` to error, see [strict enum mappings](./enum.mdx#strict-enum-mappings).

## Default Mapper configuration

Expand Down
4 changes: 4 additions & 0 deletions docs/docs/contributing/architecture.md
Expand Up @@ -37,6 +37,10 @@ The `DescriptorBuilder` does this by following this process:
one approach on how to map types (eg. an explicit cast is implemented by the `ExplicitCastMappingBuilder`).
These mappings are queued in the queue of mappings which need the body to be built (currently body builders are only used for object to object (property-based) mappings).
5. The `SourceEmitter` emits the code described by the `MapperDescriptor` and all its mappings.
The syntax objects are created by using `SyntaxFactory` and `SyntaxFactoryHelper`.
The `SyntaxFactoryHelper` tries to simplify creating formatted syntax trees.
If indentation is needed,
the `SyntaxFactoryHelper` instance of the `SourceEmitterContext`/`TypeMappingBuildContext` can be used.

## Roslyn multi targeting

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/contributing/debugging.md
Expand Up @@ -12,7 +12,7 @@ To debug the Mapperly in unit tests set a breakpoint
in the code of Mappery which you want to debug and run the tests in debug mode.

If your IDE supports the `DebugRoslynComponent` launch configuration command,
you can just set your breakpoints and debug the preconfigured `integration-tests` profile which will debug Mapperly
you can just set your breakpoints and debug the preconfigured `IntegrationTests` profile which will debug Mapperly
in the context of the integration tests.
JetBrains Rider and Visual Studio both support `DebugRoslynComponent`.
Visual Studio requires the [Roslyn SDK](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/) to be installed.
Expand Down
@@ -1,8 +1,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using Riok.Mapperly.Emit.Syntax;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;

Expand All @@ -13,12 +12,13 @@ public abstract class EnsureCapacityInfo
public abstract StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target);

protected static ExpressionStatementSyntax EnsureCapacityStatement(
SyntaxFactoryHelper syntaxFactory,
ExpressionSyntax target,
ExpressionSyntax sourceCount,
ExpressionSyntax targetCount
)
{
var sumMethod = BinaryExpression(SyntaxKind.AddExpression, sourceCount, targetCount);
return ExpressionStatement(Invocation(MemberAccess(target, EnsureCapacityName), sumMethod));
var sum = Add(sourceCount, targetCount);
return syntaxFactory.ExpressionStatement(Invocation(MemberAccess(target, EnsureCapacityName), sum));
}
}
@@ -1,6 +1,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;

Expand All @@ -25,6 +25,11 @@ public EnsureCapacityMember(string targetAccessor, string sourceAccessor)

public override StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
return EnsureCapacityStatement(target, MemberAccess(ctx.Source, _sourceAccessor), MemberAccess(target, _targetAccessor));
return EnsureCapacityStatement(
ctx.SyntaxFactory,
target,
MemberAccess(ctx.Source, _sourceAccessor),
MemberAccess(target, _targetAccessor)
);
}
}
Expand Up @@ -3,7 +3,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;

Expand Down Expand Up @@ -40,9 +40,10 @@ public override StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyn
var enumerableArgument = Argument(ctx.Source);

var outVarArgument = Argument(DeclarationExpression(VarIdentifier, SingleVariableDesignation(countIdentifier)))
.WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword));
.WithRefOrOutKeyword(TrailingSpacedToken(SyntaxKind.OutKeyword));

var getNonEnumeratedInvocation = StaticInvocation(_getNonEnumeratedMethod, enumerableArgument, outVarArgument);
return IfStatement(getNonEnumeratedInvocation, Block(EnsureCapacityStatement(target, countIdentifierName, targetCount)));
var ensureCapacity = EnsureCapacityStatement(ctx.SyntaxFactory.AddIndentation(), target, countIdentifierName, targetCount);
return ctx.SyntaxFactory.If(getNonEnumeratedInvocation, ensureCapacity);
}
}
Expand Up @@ -5,7 +5,7 @@
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Emit;
using Riok.Mapperly.Emit.Syntax;
using Riok.Mapperly.Helpers;

namespace Riok.Mapperly.Descriptors.MappingBuilders;
Expand Down
Expand Up @@ -2,7 +2,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand Down
18 changes: 7 additions & 11 deletions src/Riok.Mapperly/Descriptors/Mappings/ArrayForEachMapping.cs
Expand Up @@ -2,7 +2,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand Down Expand Up @@ -43,17 +43,17 @@ public override IEnumerable<StatementSyntax> BuildBody(TypeMappingBuildContext c
var sourceLengthArrayRank = ArrayRankSpecifier(
SingletonSeparatedList<ExpressionSyntax>(MemberAccess(ctx.Source, _countPropertyName))
);
var targetInitializationValue = ArrayCreationExpression(
var targetInitializationValue = CreateArray(
ArrayType(FullyQualifiedIdentifier(_targetArrayElementType)).WithRankSpecifiers(SingletonList(sourceLengthArrayRank))
);
yield return DeclareLocalVariable(targetVariableName, targetInitializationValue);
yield return ctx.SyntaxFactory.DeclareLocalVariable(targetVariableName, targetInitializationValue);

// var i = 0;
yield return DeclareLocalVariable(loopCounterVariableName, IntLiteral(0));
yield return ctx.SyntaxFactory.DeclareLocalVariable(loopCounterVariableName, IntLiteral(0));

// target[i] = Map(item);
var (loopItemCtx, loopItemVariableName) = ctx.WithNewSource(LoopItemVariableName);
var convertedSourceItemExpression = _elementMapping.Build(loopItemCtx);
var convertedSourceItemExpression = _elementMapping.Build(loopItemCtx.AddIndentation());

var assignment = Assignment(
ElementAccess(IdentifierName(targetVariableName), IdentifierName(loopCounterVariableName)),
Expand All @@ -63,18 +63,14 @@ public override IEnumerable<StatementSyntax> BuildBody(TypeMappingBuildContext c
// i++;
var counterIncrement = PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, IdentifierName(loopCounterVariableName));

// target[i] = Map(item);
// i++;
var assignmentBlock = Block(ExpressionStatement(assignment), ExpressionStatement(counterIncrement));

// foreach(var item in source)
//{
// target[i] = Map(item);
// i++;
//}
yield return ForEachStatement(VarIdentifier, Identifier(loopItemVariableName), ctx.Source, assignmentBlock);
yield return ctx.SyntaxFactory.ForEach(loopItemVariableName, ctx.Source, assignment, counterIncrement);

// return target;
yield return ReturnVariable(targetVariableName);
yield return ctx.SyntaxFactory.ReturnVariable(targetVariableName);
}
}
17 changes: 10 additions & 7 deletions src/Riok.Mapperly/Descriptors/Mappings/ArrayForMapping.cs
@@ -1,7 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand Down Expand Up @@ -35,25 +35,28 @@ public override IEnumerable<StatementSyntax> BuildBody(TypeMappingBuildContext c
var sourceLengthArrayRank = ArrayRankSpecifier(
SingletonSeparatedList<ExpressionSyntax>(MemberAccess(ctx.Source, ArrayLengthProperty))
);
var targetInitializationValue = ArrayCreationExpression(
var targetInitializationValue = CreateArray(
ArrayType(FullyQualifiedIdentifier(_targetArrayElementType)).WithRankSpecifiers(SingletonList(sourceLengthArrayRank))
);
yield return DeclareLocalVariable(targetVariableName, targetInitializationValue);
yield return ctx.SyntaxFactory.DeclareLocalVariable(targetVariableName, targetInitializationValue);

// target[i] = Map(source[i]);
var forLoopBuilderCtx = ctx.WithSource(ElementAccess(ctx.Source, IdentifierName(loopCounterVariableName)));
var mappedIndexedSourceValue = _elementMapping.Build(forLoopBuilderCtx);
var mappedIndexedSourceValue = _elementMapping.Build(forLoopBuilderCtx.AddIndentation());
var assignment = Assignment(
ElementAccess(IdentifierName(targetVariableName), IdentifierName(loopCounterVariableName)),
mappedIndexedSourceValue
);
var assignmentBlock = Block(SingletonList<StatementSyntax>(ExpressionStatement(assignment)));

// for(var i = 0; i < source.Length; i++)
// target[i] = Map(source[i]);
yield return IncrementalForLoop(loopCounterVariableName, assignmentBlock, MemberAccess(ctx.Source, ArrayLengthProperty));
yield return ctx.SyntaxFactory.IncrementalForLoop(
loopCounterVariableName,
MemberAccess(ctx.Source, ArrayLengthProperty),
assignment
);

// return target;
yield return ReturnVariable(targetVariableName);
yield return ctx.SyntaxFactory.ReturnVariable(targetVariableName);
}
}
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Descriptors/Mappings/CastMapping.cs
@@ -1,7 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand Down
8 changes: 2 additions & 6 deletions src/Riok.Mapperly/Descriptors/Mappings/CtorMapping.cs
@@ -1,7 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand All @@ -13,8 +12,5 @@ public class CtorMapping : NewInstanceMapping
public CtorMapping(ITypeSymbol sourceType, ITypeSymbol targetType)
: base(sourceType, targetType) { }

public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
{
return ObjectCreationExpression(FullyQualifiedIdentifier(TargetType)).WithArgumentList(ArgumentList(ctx.Source));
}
public override ExpressionSyntax Build(TypeMappingBuildContext ctx) => CreateInstance(TargetType, ctx.Source);
}
@@ -1,8 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand Down Expand Up @@ -51,10 +50,7 @@ INewInstanceMapping mapping
var castedSourceContext = ctx.WithSource(
ParenthesizedExpression(CastExpression(FullyQualifiedIdentifier(mapping.SourceType), ctx.Source))
);
return ConditionalExpression(
BinaryExpression(SyntaxKind.IsExpression, ctx.Source, FullyQualifiedIdentifier(mapping.SourceType)),
mapping.Build(castedSourceContext),
notMatched
);
var condition = Is(ctx.Source, FullyQualifiedIdentifier(mapping.SourceType));
return Conditional(condition, mapping.Build(castedSourceContext), notMatched);
}
}
@@ -1,7 +1,8 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Emit.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

Expand All @@ -25,7 +26,7 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
{
// _ => throw new ArgumentException(msg, nameof(ctx.Source)),
var sourceType = Invocation(MemberAccess(ctx.Source, GetTypeMethodName));
var fallbackArm = SwitchExpressionArm(
var fallbackArm = SwitchArm(
DiscardPattern(),
ThrowArgumentExpression(
InterpolatedString($"Cannot map {sourceType} to {TargetType.ToDisplayString()} as there is no known derived type mapping"),
Expand All @@ -38,13 +39,16 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
var arms = _typeMappings
.Select(x => BuildSwitchArm(typeArmVariableName, x.SourceType, x.Build(typeArmContext)))
.Append(fallbackArm);
return SwitchExpression(ctx.Source).WithArms(CommaSeparatedList(arms, true));
return ctx.SyntaxFactory.Switch(ctx.Source, arms);
}

private SwitchExpressionArmSyntax BuildSwitchArm(string typeArmVariableName, ITypeSymbol type, ExpressionSyntax mapping)
{
// A x => MapToADto(x),
var declaration = DeclarationPattern(FullyQualifiedIdentifier(type), SingleVariableDesignation(Identifier(typeArmVariableName)));
return SwitchExpressionArm(declaration, mapping);
var declaration = DeclarationPattern(
FullyQualifiedIdentifier(type).AddTrailingSpace(),
SingleVariableDesignation(Identifier(typeArmVariableName))
);
return SwitchArm(declaration, mapping);
}
}
@@ -1,7 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings.Enums;

Expand Down Expand Up @@ -54,7 +54,7 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
return casted;

var valueDefinedCondition = BuildIsDefinedCondition(casted);
return ConditionalExpression(valueDefinedCondition, casted, _fallback.Build(ctx));
return Conditional(valueDefinedCondition, casted, _fallback.Build(ctx));
}

private ExpressionSyntax BuildIsDefinedCondition(ExpressionSyntax convertedSourceValue)
Expand All @@ -64,7 +64,7 @@ private ExpressionSyntax BuildIsDefinedCondition(ExpressionSyntax convertedSourc
{
// (TargetEnum)v is TargetEnum.A or TargetEnum.B or ...
CheckDefinedMode.Value
=> IsPatternExpression(convertedSourceValue, OrPattern(allEnumMembers)),
=> IsPattern(convertedSourceValue, OrPattern(allEnumMembers)),

// (TargetEnum)v == ((TargetEnum)v & (TargetEnum.A | TargetEnum.B | ...))
CheckDefinedMode.Flags
Expand Down
@@ -1,7 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings.Enums;

Expand All @@ -27,7 +27,7 @@ public class EnumFallbackValueMapping : NewInstanceMapping

public IFieldSymbol? FallbackMember { get; }

public SwitchExpressionArmSyntax BuildDiscardArm(TypeMappingBuildContext ctx) => SwitchExpressionArm(DiscardPattern(), Build(ctx));
public SwitchExpressionArmSyntax BuildDiscardArm(TypeMappingBuildContext ctx) => SwitchArm(DiscardPattern(), Build(ctx));

public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
{
Expand Down
@@ -1,7 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.SyntaxFactoryHelper;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings.Enums;

Expand Down

0 comments on commit 44b5df9

Please sign in to comment.