Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
984c695
commit 831c4a1
Showing
51 changed files
with
1,267 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...k.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/INewValueTupleBuilderContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Riok.Mapperly.Descriptors.Mappings; | ||
using Riok.Mapperly.Descriptors.Mappings.MemberMappings; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
|
||
/// <summary> | ||
/// A <see cref="IMembersBuilderContext{T}"/> for mappings which create the target object | ||
/// via a tuple expression (eg. (A: source.A, B: MapToB(source.B))). | ||
/// </summary> | ||
/// <typeparam name="T">The mapping type.</typeparam> | ||
public interface INewValueTupleBuilderContext<out T> : IMembersBuilderContext<T> | ||
where T : IMapping | ||
{ | ||
void AddTupleConstructorParameterMapping(ValueTupleConstructorParameterMapping mapping); | ||
} |
22 changes: 22 additions & 0 deletions
22
.../Descriptors/MappingBodyBuilders/BuilderContext/NewValueTupleConstructorBuilderContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using Riok.Mapperly.Descriptors.Mappings; | ||
using Riok.Mapperly.Descriptors.Mappings.MemberMappings; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
|
||
/// <summary> | ||
/// An implementation of <see cref="INewValueTupleBuilderContext{T}"/>. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the mapping.</typeparam> | ||
public class NewValueTupleConstructorBuilderContext<T> : MembersMappingBuilderContext<T>, INewValueTupleBuilderContext<T> | ||
where T : INewValueTupleMapping | ||
{ | ||
public NewValueTupleConstructorBuilderContext(MappingBuilderContext builderContext, T mapping) | ||
: base(builderContext, mapping) { } | ||
|
||
public void AddTupleConstructorParameterMapping(ValueTupleConstructorParameterMapping mapping) | ||
{ | ||
MemberConfigsByRootTargetName.Remove(mapping.Parameter.Name); | ||
SetSourceMemberMapped(mapping.DelegateMapping.SourcePath); | ||
Mapping.AddConstructorParameterMapping(mapping); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...y/Descriptors/MappingBodyBuilders/BuilderContext/NewValueTupleExpressionBuilderContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using Riok.Mapperly.Descriptors.Mappings; | ||
using Riok.Mapperly.Descriptors.Mappings.MemberMappings; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
|
||
/// <summary> | ||
/// An implementation of <see cref="INewValueTupleBuilderContext{T}"/>. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the mapping.</typeparam> | ||
public class NewValueTupleExpressionBuilderContext<T> : MembersContainerBuilderContext<T>, INewValueTupleBuilderContext<T> | ||
where T : INewValueTupleMapping, IMemberAssignmentTypeMapping | ||
{ | ||
public NewValueTupleExpressionBuilderContext(MappingBuilderContext builderContext, T mapping) | ||
: base(builderContext, mapping) { } | ||
|
||
public void AddTupleConstructorParameterMapping(ValueTupleConstructorParameterMapping mapping) | ||
{ | ||
if (MemberConfigsByRootTargetName.TryGetValue(mapping.Parameter.Name, out var value)) | ||
{ | ||
value.RemoveAll(x => x.Target.Path.Count == 1); | ||
} | ||
|
||
SetSourceMemberMapped(mapping.DelegateMapping.SourcePath); | ||
Mapping.AddConstructorParameterMapping(mapping); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.CodeAnalysis; | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
using Riok.Mapperly.Descriptors.Mappings; | ||
using Riok.Mapperly.Descriptors.Mappings.MemberMappings; | ||
using Riok.Mapperly.Diagnostics; | ||
using Riok.Mapperly.Helpers; | ||
using Riok.Mapperly.Symbols; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders; | ||
|
||
public static class NewValueTupleMappingBodyBuilder | ||
{ | ||
public static void BuildMappingBody(MappingBuilderContext ctx, NewValueTupleConstructorMapping expressionMapping) | ||
{ | ||
var mappingCtx = new NewValueTupleConstructorBuilderContext<NewValueTupleConstructorMapping>(ctx, expressionMapping); | ||
BuildTupleConstructorMapping(mappingCtx); | ||
mappingCtx.AddDiagnostics(); | ||
} | ||
|
||
public static void BuildMappingBody(MappingBuilderContext ctx, NewValueTupleExpressionMapping expressionMapping) | ||
{ | ||
var mappingCtx = new NewValueTupleExpressionBuilderContext<NewValueTupleExpressionMapping>(ctx, expressionMapping); | ||
BuildTupleConstructorMapping(mappingCtx); | ||
ObjectMemberMappingBodyBuilder.BuildMappingBody(mappingCtx); | ||
mappingCtx.AddDiagnostics(); | ||
} | ||
|
||
private static void BuildTupleConstructorMapping(INewValueTupleBuilderContext<INewValueTupleMapping> ctx) | ||
{ | ||
if (ctx.Mapping.TargetType is not INamedTypeSymbol namedTargetType) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic(DiagnosticDescriptors.NoConstructorFound, ctx.BuilderContext.Target); | ||
return; | ||
} | ||
|
||
if (!TryBuildTupleConstructorMapping(ctx, namedTargetType, out var constructorParameterMappings, out var mappedTargetMemberNames)) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic(DiagnosticDescriptors.NoConstructorFound, ctx.BuilderContext.Target); | ||
return; | ||
} | ||
|
||
var removableMappedTargetMemberNames = mappedTargetMemberNames.Where(x => | ||
{ | ||
ctx.MemberConfigsByRootTargetName.TryGetValue(x, out var value); | ||
return !value?.Any() ?? true; | ||
}); | ||
|
||
ctx.TargetMembers.RemoveRange(removableMappedTargetMemberNames); | ||
foreach (var constructorParameterMapping in constructorParameterMappings) | ||
{ | ||
ctx.AddTupleConstructorParameterMapping(constructorParameterMapping); | ||
} | ||
} | ||
|
||
private static bool TryBuildTupleConstructorMapping( | ||
INewValueTupleBuilderContext<INewValueTupleMapping> ctx, | ||
INamedTypeSymbol namedTargetType, | ||
out HashSet<ValueTupleConstructorParameterMapping> constructorParameterMappings, | ||
out HashSet<string> mappedTargetMemberNames | ||
) | ||
{ | ||
mappedTargetMemberNames = new HashSet<string>(); | ||
constructorParameterMappings = new HashSet<ValueTupleConstructorParameterMapping>(); | ||
|
||
foreach (var targetMember in namedTargetType.TupleElements) | ||
{ | ||
if (!ctx.TargetMembers.ContainsKey(targetMember.Name)) | ||
{ | ||
return false; | ||
} | ||
if (!TryFindConstructorParameterSourcePath(ctx, targetMember, out var sourcePath)) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.SourceMemberNotFound, | ||
targetMember.Name, | ||
ctx.Mapping.TargetType, | ||
ctx.Mapping.SourceType | ||
); | ||
|
||
return false; | ||
} | ||
|
||
// nullability is handled inside the member expressionMapping | ||
var paramType = targetMember.Type.WithNullableAnnotation(targetMember.NullableAnnotation); | ||
var delegateMapping = | ||
ctx.BuilderContext.FindMapping(sourcePath.MemberType, paramType) | ||
?? ctx.BuilderContext.FindOrBuildMapping(sourcePath.Member.Type.NonNullable(), paramType.NonNullable()); | ||
|
||
if (delegateMapping == null) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.CouldNotMapMember, | ||
ctx.Mapping.SourceType, | ||
sourcePath.FullName, | ||
sourcePath.Member.Type, | ||
ctx.Mapping.TargetType, | ||
targetMember.Name, | ||
targetMember.Type | ||
); | ||
return false; | ||
} | ||
|
||
if (delegateMapping.Equals(ctx.Mapping)) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.ReferenceLoopInCtorMapping, | ||
ctx.Mapping.SourceType, | ||
sourcePath.FullName, | ||
ctx.Mapping.TargetType, | ||
targetMember.Name | ||
); | ||
return false; | ||
} | ||
|
||
var memberMapping = new NullMemberMapping( | ||
delegateMapping, | ||
sourcePath, | ||
paramType, | ||
ctx.BuilderContext.GetNullFallbackValue(paramType), | ||
!ctx.BuilderContext.IsExpression | ||
); | ||
|
||
var ctorMapping = new ValueTupleConstructorParameterMapping(targetMember, memberMapping); | ||
constructorParameterMappings.Add(ctorMapping); | ||
mappedTargetMemberNames.Add(targetMember.Name); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool TryFindConstructorParameterSourcePath( | ||
INewValueTupleBuilderContext<INewValueTupleMapping> ctx, | ||
IFieldSymbol field, | ||
[NotNullWhen(true)] out MemberPath? sourcePath | ||
) | ||
{ | ||
sourcePath = null; | ||
|
||
if (!ctx.MemberConfigsByRootTargetName.TryGetValue(field.Name, out var memberConfigs)) | ||
{ | ||
return TryBuildConstructorParameterSourcePath(ctx, field, out sourcePath); | ||
} | ||
|
||
if (memberConfigs.Count > 1) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.MultipleConfigurationsForConstructorParameter, | ||
field.Type, | ||
field.Name | ||
); | ||
} | ||
|
||
var memberConfig = memberConfigs.First(); | ||
if (memberConfig.Target.Path.Count > 1) | ||
{ | ||
return TryBuildConstructorParameterSourcePath(ctx, field, out sourcePath); | ||
} | ||
|
||
if (!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, memberConfig.Source.Path, out sourcePath)) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.SourceMemberNotFound, | ||
memberConfig.Source, | ||
ctx.Mapping.TargetType, | ||
ctx.Mapping.SourceType | ||
); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool TryBuildConstructorParameterSourcePath( | ||
INewValueTupleBuilderContext<INewValueTupleMapping> ctx, | ||
IFieldSymbol field, | ||
out MemberPath? sourcePath | ||
) | ||
{ | ||
var memberNameComparer = | ||
ctx.BuilderContext.MapperConfiguration.PropertyNameMappingStrategy == PropertyNameMappingStrategy.CaseSensitive | ||
? StringComparer.Ordinal | ||
: StringComparer.OrdinalIgnoreCase; | ||
|
||
if ( | ||
ctx.BuilderContext.SymbolAccessor.TryFindMemberPath( | ||
ctx.Mapping.SourceType, | ||
MemberPathCandidateBuilder.BuildMemberPathCandidates(field.Name), | ||
ctx.IgnoredSourceMemberNames, | ||
memberNameComparer, | ||
out sourcePath | ||
) | ||
) | ||
return true; | ||
|
||
// if standard matching fails, use the underlying field | ||
// if source is a tuple compare the underlying field ie, Item1, Item2 | ||
// if source is not a tuple compare top level members | ||
if (ctx.Mapping.SourceType.IsTupleType) | ||
{ | ||
if (ctx.Mapping.SourceType is not INamedTypeSymbol namedType) | ||
return false; | ||
|
||
var mappableMember = namedType.TupleElements | ||
.Where( | ||
x => | ||
x.CorrespondingTupleField != default | ||
&& !ctx.IgnoredSourceMemberNames.Contains(x.Name) | ||
&& string.Equals(field.CorrespondingTupleField!.Name, x.CorrespondingTupleField!.Name) | ||
) | ||
.Select(MappableMember.Create) | ||
.WhereNotNull() | ||
.FirstOrDefault(); | ||
|
||
if (mappableMember != default) | ||
{ | ||
sourcePath = new MemberPath(new[] { mappableMember }); | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/Riok.Mapperly/Descriptors/Mappings/INewValueTupleMapping.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using Riok.Mapperly.Descriptors.Mappings.MemberMappings; | ||
|
||
namespace Riok.Mapperly.Descriptors.Mappings; | ||
|
||
/// <summary> | ||
/// A tuple mapping creating the target instance via a tuple expression (eg. (A: 10, B: 20)). | ||
/// </summary> | ||
public interface INewValueTupleMapping : IMapping | ||
{ | ||
void AddConstructorParameterMapping(ValueTupleConstructorParameterMapping mapping); | ||
} |
Oops, something went wrong.