-
-
Notifications
You must be signed in to change notification settings - Fork 127
/
DictionaryMappingBuilder.cs
108 lines (86 loc) · 4.79 KB
/
DictionaryMappingBuilder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
namespace Riok.Mapperly.Descriptors.MappingBuilder;
public static class DictionaryMappingBuilder
{
private static readonly string _enumerableIntfName = typeof(IEnumerable<>).FullName;
private static readonly string _dictionaryClassName = typeof(Dictionary<,>).FullName;
private static readonly string _dictionaryIntfName = typeof(IDictionary<,>).FullName;
private static readonly string _readOnlyDictionaryIntfName = typeof(IReadOnlyDictionary<,>).FullName;
private static readonly string _keyValuePairName = typeof(KeyValuePair<,>).FullName;
private static readonly string _countPropertyName = "Count";
public static TypeMapping? TryBuildMapping(MappingBuilderContext ctx)
{
if (ctx.Compilation.GetTypeByMetadataName(_readOnlyDictionaryIntfName) is not { } readOnlyDictionaryIntfSymbol)
return null;
if (ctx.Compilation.GetTypeByMetadataName(_dictionaryIntfName) is not { } dictionaryIntfSymbol)
return null;
if (GetDictionaryKeyValueTypes(ctx.Target, dictionaryIntfSymbol, readOnlyDictionaryIntfSymbol) is not var (targetKeyType, targetValueType))
return null;
if (GetEnumerableKeyValueTypes(ctx, ctx.Source) is not var (sourceKeyType, sourceValueType))
return null;
var keyMapping = ctx.FindOrBuildMapping(sourceKeyType, targetKeyType);
if (keyMapping == null)
return null;
var valueMapping = ctx.FindOrBuildMapping(sourceValueType, targetValueType);
if (valueMapping == null)
return null;
// target is of type IDictionary<,> or IReadOnlyDictionary<,>. The constructed type should be Dictionary<,>
if (ctx.Compilation.GetTypeByMetadataName(_dictionaryClassName) is { } dictionaryClassSymbol
&& (IsDictionaryInterface(ctx.Target, dictionaryIntfSymbol, readOnlyDictionaryIntfSymbol) || ctx.Target.ImplementsGeneric(dictionaryClassSymbol, out _)))
{
var sourceHasCount = ctx.Source.GetAllMembers(_countPropertyName)
.OfType<IPropertySymbol>()
.Any(x => !x.IsStatic && !x.IsIndexer && !x.IsWriteOnly && x.Type.SpecialType == SpecialType.System_Int32);
var targetDictionarySymbol = dictionaryClassSymbol.Construct(targetKeyType, targetValueType);
return new ForEachAddDictionaryMapping(ctx.Source, ctx.Target, keyMapping, valueMapping, sourceHasCount, targetDictionarySymbol);
}
// the target is not a well known dictionary type
// it should have a parameterless public ctor
if (!ctx.Target.HasAccessibleParameterlessConstructor())
{
ctx.ReportDiagnostic(DiagnosticDescriptors.NoParameterlessConstructorFound, ctx.Target);
return null;
}
return new ForEachAddDictionaryMapping(ctx.Source, ctx.Target, keyMapping, valueMapping, false);
}
private static bool IsDictionaryInterface(ITypeSymbol symbol, ISymbol dictionaryIntfSymbol, ISymbol readOnlyDictionaryIntfSymbol)
{
if (symbol is not INamedTypeSymbol namedSymbol)
return false;
return SymbolEqualityComparer.Default.Equals(namedSymbol.ConstructedFrom, readOnlyDictionaryIntfSymbol)
|| SymbolEqualityComparer.Default.Equals(namedSymbol.ConstructedFrom, dictionaryIntfSymbol);
}
private static (ITypeSymbol, ITypeSymbol)? GetDictionaryKeyValueTypes(
ITypeSymbol t,
INamedTypeSymbol dictionarySymbol,
INamedTypeSymbol readOnlyDictionarySymbol)
{
if (t.ImplementsGeneric(dictionarySymbol, out var dictionaryImpl))
{
return (dictionaryImpl.TypeArguments[0], dictionaryImpl.TypeArguments[1]);
}
if (t.ImplementsGeneric(readOnlyDictionarySymbol, out var readOnlyDictionaryImpl))
{
return (readOnlyDictionaryImpl.TypeArguments[0], readOnlyDictionaryImpl.TypeArguments[1]);
}
return null;
}
private static (ITypeSymbol, ITypeSymbol)? GetEnumerableKeyValueTypes(MappingBuilderContext ctx, ITypeSymbol t)
{
if (ctx.Compilation.GetTypeByMetadataName(_enumerableIntfName) is not { } enumerableSymbol
|| ctx.Compilation.GetTypeByMetadataName(_keyValuePairName) is not { } keyValueSymbol)
{
return null;
}
if (!t.ImplementsGeneric(enumerableSymbol, out var enumerableImpl))
return null;
if (enumerableImpl.TypeArguments[0] is not INamedTypeSymbol enumeratedType)
return null;
if (!SymbolEqualityComparer.Default.Equals(enumeratedType.ConstructedFrom, keyValueSymbol))
return null;
return (enumeratedType.TypeArguments[0], enumeratedType.TypeArguments[1]);
}
}