Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC Extra parameters #622

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions src/Riok.Mapperly/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,4 @@ RMG043 | Mapper | Warning | Enum fallback values are only supported for the
RMG044 | Mapper | Warning | An ignored enum member can not be found on the source enum
RMG045 | Mapper | Warning | An ignored enum member can not be found on the target enum
RMG046 | Mapper | Error | The used C# language version is not supported by Mapperly, Mapperly requires at least C# 9.0
RMG048 | Mapper | Warning | Source parameter is not mapped to any target member
3 changes: 2 additions & 1 deletion src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ private void ExtractUserMappings()
_objectFactories,
userMapping.Method,
userMapping.SourceType,
userMapping.TargetType
userMapping.TargetType,
userMapping.AdditionalParameters
);

_mappings.Add(userMapping);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ private InlineExpressionMappingBuilderContext(
ITypeSymbol source,
ITypeSymbol target
)
: base(ctx, userSymbol, source, target, false)
: base(ctx, userSymbol, source, target, ctx.Parameters, false)
{
_parentContext = ctx;
_inlineExpressionMappings = new MappingCollection();
}

//TODO: Look at how params should be passed.
private InlineExpressionMappingBuilderContext(
InlineExpressionMappingBuilderContext ctx,
IMethodSymbol? userSymbol,
ITypeSymbol source,
ITypeSymbol target,
bool clearDerivedTypes
)
: base(ctx, userSymbol, source, target, clearDerivedTypes)
: base(ctx, userSymbol, source, target, ctx.Parameters, clearDerivedTypes)
{
_parentContext = ctx;
_inlineExpressionMappings = ctx._inlineExpressionMappings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface IMembersBuilderContext<out T>

void AddDiagnostics();

void AddTypeMapping(ITypeMapping typeMapping);

MappingBuilderContext BuilderContext { get; }

IReadOnlyCollection<string> IgnoredSourceMemberNames { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public abstract class MembersMappingBuilderContext<T> : IMembersBuilderContext<T
where T : IMapping
{
private readonly HashSet<string> _unmappedSourceMemberNames;
private readonly HashSet<string> _unmappedSourceParameters;
private readonly IReadOnlyCollection<string> _ignoredUnmatchedTargetMemberNames;
private readonly IReadOnlyCollection<string> _ignoredUnmatchedSourceMemberNames;

Expand All @@ -24,6 +25,7 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m
Mapping = mapping;
MemberConfigsByRootTargetName = GetMemberConfigurations();

_unmappedSourceParameters = BuilderContext.Parameters.Select(x => x.Name).ToHashSet();
_unmappedSourceMemberNames = GetSourceMemberNames();
TargetMembers = GetTargetMembers();

Expand Down Expand Up @@ -66,9 +68,28 @@ public void AddDiagnostics()
AddUnmatchedIgnoredSourceMembersDiagnostics();
AddUnmatchedTargetMembersDiagnostics();
AddUnmatchedSourceMembersDiagnostics();
AddUnmatchedParametersDiagnostics();
}

protected void SetSourceMemberMapped(MemberPath sourcePath) => _unmappedSourceMemberNames.Remove(sourcePath.Path.First().Name);
protected void SetSourceMemberMapped(MemberPath sourcePath)
{
var first = sourcePath.Path.First();
if (first is ParameterMember)
{
_unmappedSourceParameters.Remove(first.Name);
}
_unmappedSourceMemberNames.Remove(first.Name);
}

public void AddTypeMapping(ITypeMapping typeMapping)
{
//TODO: Delete

// foreach (var parameter in BuilderContext.Parameters)
// {
// _unmappedSourceParameters.Remove(parameter.Name);
// }
}

private HashSet<string> InitIgnoredUnmatchedProperties(IEnumerable<string> allProperties, IEnumerable<string> mappedProperties)
{
Expand Down Expand Up @@ -162,4 +183,17 @@ private void AddUnmatchedSourceMembersDiagnostics()
);
}
}

private void AddUnmatchedParametersDiagnostics()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current architecture, if the user defined mapping is not an object mapping (eg. string to int), RMG048 is not emitted.

{
foreach (var sourceParameterName in _unmappedSourceParameters)
{
BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.SourceParameterNotMapped,
sourceParameterName,
BuilderContext.Parameters.First(x => string.Equals(x.Name, sourceParameterName, StringComparison.Ordinal)).Type,
Mapping.TargetType
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static void BuildInitOnlyMemberMappings(INewInstanceBuilderContext<IMapp
ctx.IgnoredSourceMemberNames,
ignoreCase,
out var sourceMemberPath
)
) && !ObjectMemberMappingBodyBuilder.TryBuildParameterMapping(ctx, targetMember.Name, out sourceMemberPath)
)
{
ctx.BuilderContext.ReportDiagnostic(
Expand Down Expand Up @@ -102,6 +102,7 @@ IReadOnlyCollection<PropertyMappingConfiguration> memberConfigs

if (
!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, memberConfig.Source.Path, out var sourceMemberPath)
&& !ObjectMemberMappingBodyBuilder.TryFindParameterPath(ctx, memberConfig.Source.Path, out sourceMemberPath)
)
{
ctx.BuilderContext.ReportDiagnostic(
Expand Down Expand Up @@ -294,12 +295,12 @@ private static bool TryFindConstructorParameterSourcePath(
if (!ctx.MemberConfigsByRootTargetName.TryGetValue(parameter.Name, out var memberConfigs))
{
return ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(
ctx.Mapping.SourceType,
MemberPathCandidateBuilder.BuildMemberPathCandidates(parameter.Name),
ctx.IgnoredSourceMemberNames,
true,
out sourcePath
);
ctx.Mapping.SourceType,
MemberPathCandidateBuilder.BuildMemberPathCandidates(parameter.Name),
ctx.IgnoredSourceMemberNames,
true,
out sourcePath
) || ObjectMemberMappingBodyBuilder.TryBuildParameterMapping(ctx, parameter.Name, out sourcePath);
}

if (memberConfigs.Count > 1)
Expand All @@ -322,7 +323,10 @@ out sourcePath
return false;
}

if (!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, memberConfig.Source.Path, out sourcePath))
if (
!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, memberConfig.Source.Path, out sourcePath)
&& !ObjectMemberMappingBodyBuilder.TryFindParameterPath(ctx, memberConfig.Source.Path, out sourcePath)
)
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.SourceMemberNotFound,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Configuration;
using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
Expand Down Expand Up @@ -53,6 +54,12 @@ out var sourceMemberPath
continue;
}

if (TryBuildParameterMapping(ctx, targetMember.Name, out var sourceParameterPath))
{
BuildMemberAssignmentMapping(ctx, sourceParameterPath, new MemberPath(new[] { targetMember }));
continue;
}

if (targetMember.CanSet)
{
ctx.BuilderContext.ReportDiagnostic(
Expand All @@ -67,6 +74,26 @@ out var sourceMemberPath
ctx.AddDiagnostics();
}

public static bool TryBuildParameterMapping(
IMembersBuilderContext<IMapping> ctx,
string targetMemberName,
[NotNullWhen(true)] out MemberPath? sourceMemberPath
)
{
sourceMemberPath = null;
foreach (var parameter in ctx.BuilderContext.Parameters)
{
if (!string.Equals(parameter.Name, targetMemberName, StringComparison.OrdinalIgnoreCase))
continue;

var parameterMember = new ParameterMember(parameter.Type, parameter.Name);
sourceMemberPath = new MemberPath(new[] { parameterMember }, true);
return true;
}

return false;
}

private static void BuildMemberAssignmentMapping(
IMembersContainerBuilderContext<IMemberAssignmentTypeMapping> ctx,
PropertyMappingConfiguration config
Expand All @@ -84,17 +111,46 @@ PropertyMappingConfiguration config

if (!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, config.Source.Path, out var sourceMemberPath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.ConfiguredMappingSourceMemberNotFound,
config.Source.FullName,
ctx.Mapping.SourceType
);
return;
if (!TryFindParameterPath(ctx, config.Source.Path, out sourceMemberPath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.ConfiguredMappingSourceMemberNotFound,
config.Source.FullName,
ctx.Mapping.SourceType
);
return;
}
}

BuildMemberAssignmentMapping(ctx, sourceMemberPath, targetMemberPath);
}

public static bool TryFindParameterPath(
IMembersBuilderContext<IMapping> ctx,
IReadOnlyCollection<string> path,
[NotNullWhen(true)] out MemberPath? parameterPath
)
{
parameterPath = null;
if (path.Count == 0)
return false;

foreach (var parameter in ctx.BuilderContext.Parameters)
{
if (!string.Equals(parameter.Name, path.First()))
continue;

if (ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.TargetType, path.Skip(1).ToList(), out var memberPath))
{
var pathStart = new[] { new ParameterMember(parameter.Type, parameter.Name) };
parameterPath = new MemberPath(pathStart.Concat(memberPath.Path).ToList(), true);
return true;
}
}

return false;
}

public static bool ValidateMappingSpecification(
IMembersBuilderContext<IMapping> ctx,
MemberPath sourceMemberPath,
Expand Down Expand Up @@ -214,6 +270,9 @@ MemberPath targetMemberPath
return;
}

// mark used extra parameters as matched
ctx.AddTypeMapping(delegateMapping);

// no member of the source path is nullable, no null handling needed
if (!sourceMemberPath.IsAnyNullable())
{
Expand Down
19 changes: 15 additions & 4 deletions src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Riok.Mapperly.Descriptors.ObjectFactories;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors;

Expand All @@ -21,13 +22,15 @@ public MappingBuilderContext(
ObjectFactoryCollection objectFactories,
IMethodSymbol? userSymbol,
ITypeSymbol source,
ITypeSymbol target
ITypeSymbol target,
ImmutableEquatableArray<MethodParameter> parameters
)
: base(parentCtx)
{
ObjectFactories = objectFactories;
Source = source;
Target = target;
Parameters = parameters;
UserSymbol = userSymbol;
Configuration = ReadConfiguration(new MappingConfigurationReference(UserSymbol, source, target));
}
Expand All @@ -37,9 +40,10 @@ protected MappingBuilderContext(
IMethodSymbol? userSymbol,
ITypeSymbol source,
ITypeSymbol target,
ImmutableEquatableArray<MethodParameter> parameters,
bool clearDerivedTypes
)
: this(ctx, ctx.ObjectFactories, userSymbol, source, target)
: this(ctx, ctx.ObjectFactories, userSymbol, source, target, parameters)
{
if (clearDerivedTypes)
{
Expand All @@ -53,6 +57,8 @@ bool clearDerivedTypes

public ITypeSymbol Target { get; }

public ImmutableEquatableArray<MethodParameter> Parameters { get; } = ImmutableEquatableArray.Empty<MethodParameter>();

public CollectionInfos? CollectionInfos => _collectionInfos ??= CollectionInfoBuilder.Build(Types, SymbolAccessor, Source, Target);

protected IMethodSymbol? UserSymbol { get; }
Expand All @@ -77,6 +83,8 @@ bool clearDerivedTypes
public virtual ITypeMapping? FindMapping(ITypeSymbol sourceType, ITypeSymbol targetType) =>
MappingBuilder.Find(sourceType.UpgradeNullable(), targetType.UpgradeNullable());

//TODO: updagrade nullable

/// <summary>
/// Tries to find an existing mapping for the provided types.
/// If none is found, a new one is created.
Expand Down Expand Up @@ -131,7 +139,9 @@ bool clearDerivedTypes
ITypeSymbol sourceType,
ITypeSymbol targetType,
MappingBuildingOptions options = MappingBuildingOptions.Default
) => ExistingTargetMappingBuilder.Find(sourceType, targetType) ?? BuildExistingTargetMapping(sourceType, targetType, options);
) =>
ExistingTargetMappingBuilder.Find(sourceType, targetType, Parameters)
?? BuildExistingTargetMapping(sourceType, targetType, options);

/// <summary>
/// Tries to build an existing target instance mapping.
Expand Down Expand Up @@ -190,7 +200,8 @@ protected virtual MappingBuilderContext ContextForMapping(
MappingBuildingOptions options
)
{
return new(this, userSymbol, sourceType, targetType, options.HasFlag(MappingBuildingOptions.ClearDerivedTypes));
var parameters = userSymbol == null ? ImmutableEquatableArray<MethodParameter>.Empty : Parameters;
return new(this, userSymbol, sourceType, targetType, parameters, options.HasFlag(MappingBuildingOptions.ClearDerivedTypes));
}

protected ITypeMapping? BuildMapping(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors.MappingBuilders;

Expand All @@ -24,8 +25,11 @@ public ExistingTargetMappingBuilder(MappingCollection mappings)
_mappings = mappings;
}

public IExistingTargetMapping? Find(ITypeSymbol sourceType, ITypeSymbol targetType) =>
_mappings.FindExistingInstanceMapping(sourceType, targetType);
public IExistingTargetMapping? Find(
ITypeSymbol sourceType,
ITypeSymbol targetType,
ImmutableEquatableArray<MethodParameter> parameters
) => _mappings.FindExistingInstanceMapping(sourceType, targetType, parameters);

public IExistingTargetMapping? Build(MappingBuilderContext ctx, bool resultIsReusable)
{
Expand Down
7 changes: 6 additions & 1 deletion src/Riok.Mapperly/Descriptors/MappingCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Descriptors.Mappings.UserMappings;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors;

Expand Down Expand Up @@ -45,7 +46,11 @@ public class MappingCollection
return mapping;
}

public IExistingTargetMapping? FindExistingInstanceMapping(ITypeSymbol sourceType, ITypeSymbol targetType)
public IExistingTargetMapping? FindExistingInstanceMapping(
ITypeSymbol sourceType,
ITypeSymbol targetType,
ImmutableEquatableArray<MethodParameter> parameters
)
{
_existingTargetMappings.TryGetValue(new TypeMappingKey(sourceType, targetType), out var mapping);
return mapping;
Expand Down
Loading