-
-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
- Loading branch information
There are no files selected for viewing
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); | ||
} |
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
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)) | ||
{ | ||
// remove the mapping used to map the tuple constructor | ||
value.RemoveAll(x => x.Target.Path.Count == 1); | ||
|
||
// remove form dictionary if there aren't nested mappings | ||
if (!value.Any()) | ||
{ | ||
MemberConfigsByRootTargetName.Remove(mapping.Parameter.Name); | ||
TargetMembers.Remove(mapping.Parameter.Name); | ||
} | ||
} | ||
|
||
SetSourceMemberMapped(mapping.DelegateMapping.SourcePath); | ||
Mapping.AddConstructorParameterMapping(mapping); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
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; | ||
Check warning on line 35 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs Codecov / codecov/patchsrc/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs#L33-L35
|
||
} | ||
|
||
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.ContainsKey(x)); | ||
|
||
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; | ||
Check warning on line 99 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs Codecov / codecov/patchsrc/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs#L89-L99
|
||
} | ||
|
||
if (delegateMapping.Equals(ctx.Mapping)) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.ReferenceLoopInCtorMapping, | ||
ctx.Mapping.SourceType, | ||
sourcePath.FullName, | ||
ctx.Mapping.TargetType, | ||
targetMember.Name | ||
); | ||
return false; | ||
Check warning on line 111 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs Codecov / codecov/patchsrc/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs#L103-L111
|
||
} | ||
|
||
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); | ||
} | ||
|
||
// remove nested targets | ||
var initMemberPaths = memberConfigs.Where(x => x.Target.Path.Count == 1).ToArray(); | ||
|
||
// if all memberConfigs are nested than do normal mapping | ||
if (initMemberPaths.Length == 0) | ||
{ | ||
return TryBuildConstructorParameterSourcePath(ctx, field, out sourcePath); | ||
} | ||
|
||
if (initMemberPaths.Length > 1) | ||
{ | ||
ctx.BuilderContext.ReportDiagnostic( | ||
DiagnosticDescriptors.MultipleConfigurationsForConstructorParameter, | ||
field.Type, | ||
field.Name | ||
); | ||
} | ||
|
||
var memberConfig = initMemberPaths.First(); | ||
|
||
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; | ||
Check warning on line 171 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs Codecov / codecov/patchsrc/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs#L164-L171
|
||
} | ||
|
||
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; | ||
Check warning on line 205 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs Codecov / codecov/patchsrc/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewValueTupleMappingBodyBuilder.cs#L205
|
||
|
||
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; | ||
} | ||
} |