From aec715f53877db889850f7a036bcf6742069fa3b Mon Sep 17 00:00:00 2001 From: PieterjanDeClippel Date: Mon, 29 Sep 2025 09:25:16 +0200 Subject: [PATCH 1/3] Allow mapping to/from Dictionary-types --- .../MapAsDictionaryAttribute.cs | 9 +++++++++ .../Generators/MapperGenerator.Producer.cs | 18 ++++++++++++++++++ .../Generators/MapperGenerator.cs | 2 ++ .../Models/PropertyDeclaration.cs | 2 ++ .../TestProjects/MapperDebugging/Program.cs | 13 +++++++++---- 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 SourceGenerators/Mapper/MintPlayer.Mapper.Attributes/MapAsDictionaryAttribute.cs diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper.Attributes/MapAsDictionaryAttribute.cs b/SourceGenerators/Mapper/MintPlayer.Mapper.Attributes/MapAsDictionaryAttribute.cs new file mode 100644 index 00000000..8131ed28 --- /dev/null +++ b/SourceGenerators/Mapper/MintPlayer.Mapper.Attributes/MapAsDictionaryAttribute.cs @@ -0,0 +1,9 @@ +namespace MintPlayer.Mapper.Attributes; + +/// +/// Indicates that this type should be treated as a dictionary when mapping. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public sealed class MapAsDictionaryAttribute : Attribute +{ +} diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs index 6a9fb3ca..d2848e67 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs @@ -318,6 +318,24 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio else writer.WriteLine($"{prefix}input.{destination.PropertyName}{suffix}"); } + else if (source.HasStringIndexer && destination.IsPrimitive) + { + //if (source.PropertyType != destination.PropertyType) + // writer.WriteLine($"{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}){suffix}"); + //else if (source.StateName != null && destination.StateName != null) + // writer.WriteLine($"""{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}, {source.StateName}, {destination.StateName}){suffix}"""); + //else + // writer.WriteLine($"{prefix}input.{destination.PropertyName}{suffix}"); + } + else if (source.IsPrimitive && destination.HasStringIndexer) + { + //if (source.PropertyType != destination.PropertyType) + // writer.WriteLine($"{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}){suffix}"); + //else if (source.StateName != null && destination.StateName != null) + // writer.WriteLine($"""{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}, {source.StateName}, {destination.StateName}){suffix}"""); + //else + // writer.WriteLine($"{prefix}input.{destination.PropertyName}{suffix}"); + } // Handle arrays else if (IsArrayType(source.PropertyType) && IsArrayType(destination.PropertyType)) { diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs index b0046455..77baab80 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs @@ -277,6 +277,8 @@ private static string CreateMethodName(TypedConstant preferred, INamedTypeSymbol //IsAbstract = p.IsAbstract, //IsOverride = p.IsOverride, IsPrimitive = p.Type.IsValueType || p.Type.SpecialType == SpecialType.System_String, + HasStringIndexer = p.Type is INamedTypeSymbol namedType2 && namedType2.GetMembers().OfType().Any(pi => pi.IsIndexer && pi.Parameters.Length == 1 && pi.Parameters[0].Type.SpecialType == SpecialType.System_String), + ShouldMapAsDictionary = p.Type.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), }); } diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs index bc37b746..cbc502c3 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs @@ -12,6 +12,8 @@ public partial class PropertyDeclaration public int? StateName { get; set; } public bool IsStatic { get; set; } public bool IsPrimitive { get; set; } + public bool HasStringIndexer { get; set; } + public bool ShouldMapAsDictionary { get; set; } public override string ToString() => $"{PropertyName} ({PropertyType})"; } diff --git a/SourceGenerators/TestProjects/MapperDebugging/Program.cs b/SourceGenerators/TestProjects/MapperDebugging/Program.cs index d9e54ea8..16bc8fcf 100644 --- a/SourceGenerators/TestProjects/MapperDebugging/Program.cs +++ b/SourceGenerators/TestProjects/MapperDebugging/Program.cs @@ -128,17 +128,22 @@ public class PersonDto [GenerateMapper(typeof(AddressDto))] public class Address { + [MapperAlias("Straatnaam")] public string? Street { get; set; } + [MapperAlias("Stad")] public string? City { get; set; } } +[MapAsDictionary] public class AddressDto { - [MapperAlias(nameof(Address.Street))] - public string? Straatnaam { get; set; } + private readonly Dictionary data = new(); - [MapperAlias(nameof(Address.City))] - public string? Stad { get; set; } + public object? this[string key] + { + get => data.TryGetValue(key, out object? value) ? value : null; + set => data[key] = value; + } } public class ContactInfo From 0858d7d16daec0e13667ec48b391600b5d8665cd Mon Sep 17 00:00:00 2001 From: PieterjanDeClippel Date: Tue, 30 Sep 2025 12:17:59 +0200 Subject: [PATCH 2/3] Temp commit --- .../Generators/MapperGenerator.Producer.cs | 70 +++++++++++-------- .../Generators/MapperGenerator.cs | 36 ++++++++-- .../Models/PropertyDeclaration.cs | 2 +- .../MintPlayer.Mapper/Models/TypeToMap.cs | 10 +++ .../Models/TypeWithMappedProperties.cs | 2 +- .../Extensions/EnumerableExtensions.cs | 25 +++++++ 6 files changed, 106 insertions(+), 39 deletions(-) diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs index d2848e67..77f94bf1 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs @@ -112,7 +112,7 @@ protected override void ProduceSource(IndentedTextWriter writer, CancellationTok foreach (var (source, destination) in type.MappedProperties) { - HandleProperty(writer, source, destination, EWriteType.Assignment); + HandleProperty(writer, source, destination, type.TypeToMap.SourceTypeHasIndexer, type.TypeToMap.DestinationTypeHasIndexer, EWriteType.Assignment); } writer.Indent--; @@ -134,7 +134,7 @@ protected override void ProduceSource(IndentedTextWriter writer, CancellationTok foreach (var (source, destination) in type.MappedProperties) { - HandleProperty(writer, source, destination, EWriteType.Initializer); + HandleProperty(writer, source, destination, type.TypeToMap.SourceTypeHasIndexer, type.TypeToMap.DestinationTypeHasIndexer, EWriteType.Initializer); } writer.Indent--; @@ -155,7 +155,7 @@ protected override void ProduceSource(IndentedTextWriter writer, CancellationTok foreach (var (source, destination) in type.MappedProperties) { - HandleProperty(writer, destination, source, EWriteType.Assignment); + HandleProperty(writer, destination, source, type.TypeToMap.DestinationTypeHasIndexer, type.TypeToMap.SourceTypeHasIndexer, EWriteType.Assignment); } writer.Indent--; @@ -178,7 +178,7 @@ protected override void ProduceSource(IndentedTextWriter writer, CancellationTok // TODO: add properties foreach (var (source, destination) in type.MappedProperties) { - HandleProperty(writer, destination, source, EWriteType.Initializer); + HandleProperty(writer, destination, source, type.TypeToMap.DestinationTypeHasIndexer, type.TypeToMap.SourceTypeHasIndexer, EWriteType.Initializer); } writer.Indent--; @@ -292,15 +292,23 @@ private static string GetElementType(string collectionType) return collectionType; } - private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaration source, PropertyDeclaration destination, EWriteType writeType) + private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaration? source, PropertyDeclaration? destination, bool sourceWithIndexer, bool destinationWithIndexer, EWriteType writeType) { - var prefix = writeType switch + var sourceValueAccessor = (sourceWithIndexer, writeType) switch { - EWriteType.Initializer => $"{source.PropertyName} = ", - EWriteType.Assignment => $"output.{source.PropertyName} = ", + (true, EWriteType.Initializer) => $"{source.PropertyName} = ", + (true, EWriteType.Assignment) => $"output.{source.PropertyName} = ", + (false, EWriteType.Initializer) => $"[{destination!.PropertyName}] = ", + (false, EWriteType.Assignment) => $"output.{source.PropertyName} = ", _ => throw new NotImplementedException(), }; + var destinationValueAccessor = $"input.{destination.PropertyName}"; + + //var destinationValueAccessor = destination.ShouldMapAsDictionary + // ? $"input[\"{destination.Alias ?? destination.PropertyName}\"] as {destination.PropertyType}" + // : $"input.{destination.PropertyName}"; + var suffix = writeType switch { EWriteType.Initializer => ",", @@ -312,29 +320,29 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio if (source.IsPrimitive && destination.IsPrimitive) { if (source.PropertyType != destination.PropertyType) - writer.WriteLine($"{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}){suffix}"); + writer.WriteLine($"{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}){suffix}"); else if (source.StateName != null && destination.StateName != null) - writer.WriteLine($"""{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}, {source.StateName}, {destination.StateName}){suffix}"""); + writer.WriteLine($"""{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}, {source.StateName}, {destination.StateName}){suffix}"""); else - writer.WriteLine($"{prefix}input.{destination.PropertyName}{suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}{suffix}"); } else if (source.HasStringIndexer && destination.IsPrimitive) { - //if (source.PropertyType != destination.PropertyType) - // writer.WriteLine($"{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}){suffix}"); - //else if (source.StateName != null && destination.StateName != null) - // writer.WriteLine($"""{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}, {source.StateName}, {destination.StateName}){suffix}"""); - //else - // writer.WriteLine($"{prefix}input.{destination.PropertyName}{suffix}"); + if (source.PropertyType != destination.PropertyType) + writer.WriteLine($"{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}){suffix}"); + else if (source.StateName != null && destination.StateName != null) + writer.WriteLine($"""{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}, {source.StateName}, {destination.StateName}){suffix}"""); + else + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}{suffix}"); } else if (source.IsPrimitive && destination.HasStringIndexer) { - //if (source.PropertyType != destination.PropertyType) - // writer.WriteLine($"{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}){suffix}"); - //else if (source.StateName != null && destination.StateName != null) - // writer.WriteLine($"""{prefix}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>(input.{destination.PropertyName}, {source.StateName}, {destination.StateName}){suffix}"""); - //else - // writer.WriteLine($"{prefix}input.{destination.PropertyName}{suffix}"); + if (source.PropertyType != destination.PropertyType) + writer.WriteLine($"{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}){suffix}"); + else if (source.StateName != null && destination.StateName != null) + writer.WriteLine($"""{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}, {source.StateName}, {destination.StateName}){suffix}"""); + else + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}{suffix}"); } // Handle arrays else if (IsArrayType(source.PropertyType) && IsArrayType(destination.PropertyType)) @@ -342,12 +350,12 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio var elementType = GetElementType(source.PropertyType); if (IsPrimitiveOrString(elementType)) { - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.ToArray(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.ToArray(){suffix}"); } else { var shortName = elementType.WithoutGlobal().Split('.').Last(); - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.Select(x => x.MapTo{shortName}()).ToArray(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.Select(x => x.MapTo{shortName}()).ToArray(){suffix}"); } } // Handle List @@ -356,12 +364,12 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio var elementType = GetElementType(source.PropertyType); if (IsPrimitiveOrString(elementType)) { - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.ToList(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.ToList(){suffix}"); } else { var shortName = elementType.WithoutGlobal().Split('.').Last(); - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.Select(x => x.MapTo{shortName}()).ToList(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.Select(x => x.MapTo{shortName}()).ToList(){suffix}"); } } // Handle ICollection @@ -370,23 +378,23 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio var elementType = GetElementType(source.PropertyType); if (IsPrimitiveOrString(elementType)) { - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.ToList(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.ToList(){suffix}"); } else { var shortName = elementType.WithoutGlobal().Split('.').Last(); - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.Select(x => x.MapTo{shortName}()).ToList(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.Select(x => x.MapTo{shortName}()).ToList(){suffix}"); } } // Handle nullable reference types else if (IsNullableType(source.PropertyType) && IsNullableType(destination.PropertyType)) { - writer.WriteLine($"{prefix}input.{destination.PropertyName} == null ? null : input.{destination.PropertyName}.MapTo{source.PropertyTypeName}(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.MapTo{source.PropertyTypeName}(){suffix}"); } // Handle complex types else { - writer.WriteLine($"{prefix}input.{destination.PropertyName}.MapTo{source.PropertyTypeName}(){suffix}"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}.MapTo{source.PropertyTypeName}(){suffix}"); } } } diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs index 77baab80..6c14dbce 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using MintPlayer.Mapper.Models; using MintPlayer.SourceGenerators.Tools; using MintPlayer.SourceGenerators.Tools.Extensions; @@ -31,7 +32,7 @@ public override void Initialize(IncrementalGeneratorInitializationContext contex //var destTypeMethodName = destAttr?.ConstructorArguments.ElementAtOrDefault(1); var mappingMethodName = destType1.Name.EnsureStartsWith("MapTo"); var declaredMethodName = sourceType.Name.EnsureStartsWith("MapTo"); - return new Models.TypeToMap + var res = new Models.TypeToMap { DestinationNamespace = sourceType.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)), DeclaredType = sourceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), @@ -44,10 +45,13 @@ public override void Initialize(IncrementalGeneratorInitializationContext contex AppliedOn = Models.EAppliedOn.Assembly, HasError = false, Location = attr1.ApplicationSyntaxReference?.GetSyntax(ct)?.GetLocation() ?? Location.None, + SourceTypeHasIndexer = sourceType.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), + DestinationTypeHasIndexer = destType1.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), DeclaredProperties = ProcessProperties(sourceType).ToArray(), MappingProperties = ProcessProperties(destType1).ToArray(), }; + return res; } else { @@ -74,7 +78,7 @@ public override void Initialize(IncrementalGeneratorInitializationContext contex var destAttr = destType2.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.GenerateMapperAttribute"); var destTypeMethodName = destAttr?.ConstructorArguments.ElementAtOrDefault(1); var mappingMethodName = destTypeMethodName is null ? destType2.Name.EnsureStartsWith("MapTo") : CreateMethodName((TypedConstant)destTypeMethodName, destType2); - return new Models.TypeToMap + var res = new Models.TypeToMap { DestinationNamespace = typeSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)), DeclaredType = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), @@ -87,10 +91,13 @@ public override void Initialize(IncrementalGeneratorInitializationContext contex AppliedOn = Models.EAppliedOn.Class, HasError = false, Location = attr2.ApplicationSyntaxReference?.GetSyntax(ct)?.GetLocation() ?? Location.None, + SourceTypeHasIndexer = typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), + DestinationTypeHasIndexer = destType2.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), DeclaredProperties = ProcessProperties(typeSymbol).ToArray(), MappingProperties = ProcessProperties(destType2).ToArray(), }; + return res; } else { @@ -155,9 +162,26 @@ public override void Initialize(IncrementalGeneratorInitializationContext contex .Select(static (i, ct) => new Models.TypeWithMappedProperties { TypeToMap = i, - MappedProperties = i.DeclaredProperties - .Select(dp => (Source: dp, Destination: i.MappingProperties.FirstOrDefault(mp => mp.Alias == dp.Alias))) - .Where(p => p.Source is { IsStatic: false } && p.Destination is { IsStatic: false }) + //MappedProperties = i.DeclaredProperties + // .Select(dp => (Source: dp, Destination: i.MappingProperties.FirstOrDefault(mp => mp.Alias == dp.Alias))) + // .Where(p => p.Source is { IsStatic: false } && p.Destination is { IsStatic: false }) + + MappedProperties = (i.SourceTypeHasIndexer, i.DestinationTypeHasIndexer) switch + { + (true, true) => [], + (true, false) => i.MappingProperties + .Where(mp => !mp.IsStatic) + .Select(mp => (Source: (PropertyDeclaration?)null, Destination: mp.AsNullable())) + .Where(p => p.Destination is { IsStatic: false }), + (false, true) => i.DeclaredProperties + .Where(dp => !dp.IsStatic) + .Select(dp => (Source: dp.AsNullable(), Destination: (PropertyDeclaration?)null)) + .Where(p => p.Source is { IsStatic: false }), + (false, false) => i.DeclaredProperties + .Where(dp => !dp.IsStatic) + .Select(dp => (Source: dp.AsNullable(), Destination: (PropertyDeclaration?)i.MappingProperties.FirstOrDefault(mp => mp.Alias == dp.Alias))) + .Where(p => p.Source is { IsStatic: false } && p.Destination is { IsStatic: false }), + } }) .WithComparer() .Collect(); @@ -278,7 +302,7 @@ private static string CreateMethodName(TypedConstant preferred, INamedTypeSymbol //IsOverride = p.IsOverride, IsPrimitive = p.Type.IsValueType || p.Type.SpecialType == SpecialType.System_String, HasStringIndexer = p.Type is INamedTypeSymbol namedType2 && namedType2.GetMembers().OfType().Any(pi => pi.IsIndexer && pi.Parameters.Length == 1 && pi.Parameters[0].Type.SpecialType == SpecialType.System_String), - ShouldMapAsDictionary = p.Type.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), + //ShouldMapAsDictionary = p.Type.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "MintPlayer.Mapper.Attributes.MapAsDictionaryAttribute"), }); } diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs index cbc502c3..14a89963 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/PropertyDeclaration.cs @@ -13,7 +13,7 @@ public partial class PropertyDeclaration public bool IsStatic { get; set; } public bool IsPrimitive { get; set; } public bool HasStringIndexer { get; set; } - public bool ShouldMapAsDictionary { get; set; } + //public bool ShouldMapAsDictionary { get; set; } public override string ToString() => $"{PropertyName} ({PropertyType})"; } diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeToMap.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeToMap.cs index 7c27be72..e24eefc3 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeToMap.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeToMap.cs @@ -37,6 +37,16 @@ public partial class TypeToMap /// public bool AreBothDecorated { get; set; } + /// + /// Indicates that the source type has an indexer + /// + public bool SourceTypeHasIndexer { get; set; } + + /// + /// Indicates that the destination type has an indexer + /// + public bool DestinationTypeHasIndexer { get; set; } + public EAppliedOn AppliedOn { get; set; } public bool HasError { get; set; } diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeWithMappedProperties.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeWithMappedProperties.cs index 6cb9a5c1..7cffeab9 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeWithMappedProperties.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Models/TypeWithMappedProperties.cs @@ -6,5 +6,5 @@ namespace MintPlayer.Mapper.Models; public partial class TypeWithMappedProperties { public TypeToMap TypeToMap { get; set; } = null!; - public IEnumerable<(PropertyDeclaration Source, PropertyDeclaration Destination)> MappedProperties { get; set; } = []; + public IEnumerable<(PropertyDeclaration? Source, PropertyDeclaration? Destination)> MappedProperties { get; set; } = []; } \ No newline at end of file diff --git a/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/EnumerableExtensions.cs b/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/EnumerableExtensions.cs index 79075ad2..315dd20f 100644 --- a/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/EnumerableExtensions.cs +++ b/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/EnumerableExtensions.cs @@ -10,4 +10,29 @@ public static IEnumerable NotNull(this IEnumerable source) where T : c { return source.Where(item => item is not null).Cast(); } + + /// + /// Converts a sequence of value types to a sequence of nullable value types of the same underlying type. + /// + /// This method enables LINQ queries and other operations that require nullable value types when + /// working with sequences of non-nullable value types. The order and number of elements in the returned sequence + /// are the same as in the source sequence. + /// The value type of the elements in the source sequence. + /// The sequence of value type elements to convert. Cannot be null. + /// An IEnumerable containing the elements of the source sequence, each cast to a nullable value type. + public static IEnumerable AsNullable(this IEnumerable source) where T : struct + { + return source.Cast(); + } + + /// + /// Converts a reference type value to its nullable equivalent. + /// + /// The reference type to convert to a nullable type. + /// The reference type value to convert. Can be null. + /// A nullable value of type T that represents the original value, or null if the source is null. + public static T? AsNullable(this T source) where T : class + { + return (T?)source; + } } From 093864a0b820f5b6867b3e583e48c66a49a1ca57 Mon Sep 17 00:00:00 2001 From: PieterjanDeClippel Date: Thu, 2 Oct 2025 12:58:55 +0200 Subject: [PATCH 3/3] Temp commit --- .../Generators/MapperGenerator.Producer.cs | 75 ++++++++----------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs index 77f94bf1..598c61c0 100644 --- a/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs +++ b/SourceGenerators/Mapper/MintPlayer.Mapper/Generators/MapperGenerator.Producer.cs @@ -218,21 +218,21 @@ protected override void ProduceSource(IndentedTextWriter writer, CancellationTok } // Helper methods for type checks - private static bool IsArrayType(string type) - => type.EndsWith("[]"); + private static bool IsArrayType(string? type) + => type is not null && type.EndsWith("[]"); - private static bool IsListType(string type) - => type.StartsWith("global::System.Collections.Generic.List<") || type.StartsWith("List<"); + private static bool IsListType(string? type) + => type is not null && (type.StartsWith("global::System.Collections.Generic.List<") || type.StartsWith("List<")); - private static bool IsCollectionType(string type) - => type.StartsWith("global::System.Collections.Generic.ICollection<") || type.StartsWith("ICollection<"); + private static bool IsCollectionType(string? type) + => type is not null && (type.StartsWith("global::System.Collections.Generic.ICollection<") || type.StartsWith("ICollection<")); - private static bool IsNullableType(string type) - => type.EndsWith("?") || type.StartsWith("System.Nullable<"); + private static bool IsNullableType(string? type) + => type is not null && (type.EndsWith("?") || type.StartsWith("System.Nullable<")); - private static bool IsPrimitiveOrString(string type) + private static bool IsPrimitiveOrString(string? type) { - switch (type.WithoutGlobal()) + switch (type?.WithoutGlobal()) { case "string": case "System.String": @@ -294,20 +294,24 @@ private static string GetElementType(string collectionType) private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaration? source, PropertyDeclaration? destination, bool sourceWithIndexer, bool destinationWithIndexer, EWriteType writeType) { + var destProp = destination is { } ? destination.Alias ?? destination.PropertyName : null; + var sourceProp = source is { } ? source.Alias ?? source.PropertyName : null; + + destProp ??= sourceProp; + sourceProp ??= destProp; + var sourceValueAccessor = (sourceWithIndexer, writeType) switch { - (true, EWriteType.Initializer) => $"{source.PropertyName} = ", - (true, EWriteType.Assignment) => $"output.{source.PropertyName} = ", - (false, EWriteType.Initializer) => $"[{destination!.PropertyName}] = ", - (false, EWriteType.Assignment) => $"output.{source.PropertyName} = ", + (true, EWriteType.Initializer) => $"[{destProp}] = ", + (true, EWriteType.Assignment) => $"output[{destProp}] = ", + (false, EWriteType.Initializer) => $"{destProp} = ", + (false, EWriteType.Assignment) => $"output.{destProp} = ", _ => throw new NotImplementedException(), }; - var destinationValueAccessor = $"input.{destination.PropertyName}"; - - //var destinationValueAccessor = destination.ShouldMapAsDictionary - // ? $"input[\"{destination.Alias ?? destination.PropertyName}\"] as {destination.PropertyType}" - // : $"input.{destination.PropertyName}"; + var destinationValueAccessor = destinationWithIndexer + ? $""""input["{destProp}"]"""" + : $"input.{destProp}"; var suffix = writeType switch { @@ -317,25 +321,9 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio }; // Handle primitive types - if (source.IsPrimitive && destination.IsPrimitive) - { - if (source.PropertyType != destination.PropertyType) - writer.WriteLine($"{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}){suffix}"); - else if (source.StateName != null && destination.StateName != null) - writer.WriteLine($"""{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}, {source.StateName}, {destination.StateName}){suffix}"""); - else - writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}{suffix}"); - } - else if (source.HasStringIndexer && destination.IsPrimitive) - { - if (source.PropertyType != destination.PropertyType) - writer.WriteLine($"{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}){suffix}"); - else if (source.StateName != null && destination.StateName != null) - writer.WriteLine($"""{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}, {source.StateName}, {destination.StateName}){suffix}"""); - else - writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}{suffix}"); - } - else if (source.IsPrimitive && destination.HasStringIndexer) + if ((source is { IsPrimitive: true } && destination is { IsPrimitive: true }) || + (source is { HasStringIndexer: true } && destination is { IsPrimitive: true }) || + (source is { IsPrimitive: true } && destination is { HasStringIndexer: true })) { if (source.PropertyType != destination.PropertyType) writer.WriteLine($"{sourceValueAccessor}ConvertProperty<{destination.PropertyType}, {source.PropertyType}>({destinationValueAccessor}){suffix}"); @@ -345,7 +333,7 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}{suffix}"); } // Handle arrays - else if (IsArrayType(source.PropertyType) && IsArrayType(destination.PropertyType)) + else if (source is not null && IsArrayType(source.PropertyType) && IsArrayType(destination?.PropertyType)) { var elementType = GetElementType(source.PropertyType); if (IsPrimitiveOrString(elementType)) @@ -359,7 +347,7 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio } } // Handle List - else if (IsListType(source.PropertyType) && IsListType(destination.PropertyType)) + else if (source is not null && IsListType(source.PropertyType) && IsListType(destination?.PropertyType)) { var elementType = GetElementType(source.PropertyType); if (IsPrimitiveOrString(elementType)) @@ -373,7 +361,7 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio } } // Handle ICollection - else if (IsCollectionType(source.PropertyType) && IsCollectionType(destination.PropertyType)) + else if (source is not null && IsCollectionType(source.PropertyType) && IsCollectionType(destination?.PropertyType)) { var elementType = GetElementType(source.PropertyType); if (IsPrimitiveOrString(elementType)) @@ -387,14 +375,15 @@ private static void HandleProperty(IndentedTextWriter writer, PropertyDeclaratio } } // Handle nullable reference types - else if (IsNullableType(source.PropertyType) && IsNullableType(destination.PropertyType)) + else if (source is not null && IsNullableType(source.PropertyType) && IsNullableType(destination?.PropertyType)) { writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor} == null ? null : {destinationValueAccessor}.MapTo{source.PropertyTypeName}(){suffix}"); } // Handle complex types else { - writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}.MapTo{source.PropertyTypeName}(){suffix}"); + var type = source?.PropertyTypeName ?? destination?.PropertyTypeName ?? throw new InvalidOperationException("Both source and destination are null"); + writer.WriteLine($"{sourceValueAccessor}{destinationValueAccessor}.MapTo{type}(){suffix}"); } } }