diff --git a/src/DynamoDBGenerator.SourceGenerator/Constants.cs b/src/DynamoDBGenerator.SourceGenerator/Constants.cs index 87cf7dc7..d50bf8fa 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Constants.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Constants.cs @@ -81,6 +81,7 @@ public static class Marshaller public static class AttributeValueUtilityFactory { private const string ClassName = "MarshallHelper"; + public const string ToAttributeValue = $"{ClassName}.ToAttributeValue"; public const string Null = $"{ClassName}.Null"; public const string ToList = $"{ClassName}.ToList"; public const string ToArray = $"{ClassName}.ToArray"; diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/CompilationExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/CompilationExtensions.cs index b0a31f55..32e858bf 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/CompilationExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/CompilationExtensions.cs @@ -1,13 +1,13 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace DynamoDBGenerator.SourceGenerator.Extensions; // Great source: https://github.com/dotnet/runtime/blob/main/src/tools/illink/src/ILLink.RoslynAnalyzer/CompilationExtensions.cs public static class CompilationExtensions { - public static ReadOnlySpan GetTypeSymbols(this Compilation compilation, ImmutableArray classDeclarations) + public static ReadOnlySpan GetTypeSymbols(this Compilation compilation, + ImmutableArray classDeclarations) { var span = classDeclarations.AsSpan(); var symbols = new INamedTypeSymbol[classDeclarations.Length]; @@ -19,7 +19,8 @@ public static ReadOnlySpan GetTypeSymbols(this Compilation com .GetSemanticModel(classDeclarationSyntax.SyntaxTree) .GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol typeSymbol) - throw new ArgumentException($"Could not convert the '{classDeclarationSyntax.ToFullString()}' into a '{nameof(ITypeSymbol)}'."); + throw new ArgumentException( + $"Could not convert the '{classDeclarationSyntax.ToFullString()}' into a '{nameof(ITypeSymbol)}'."); symbols[i] = typeSymbol; } diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/EnumerableExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/EnumerableExtensions.cs index a1a59254..ba772735 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/EnumerableExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/EnumerableExtensions.cs @@ -16,12 +16,11 @@ public static IEnumerable AllButLast(this IEnumerable enumerable, Func< buffer = item; } - if (isBuffered) - { - yield return buffer!; - } + if (isBuffered) yield return buffer!; } - public static IEnumerable AllAndLast(this IEnumerable enumerable, Func @default, Func onLast) + + public static IEnumerable AllAndLast(this IEnumerable enumerable, Func @default, + Func onLast) { var buffer = default(T); var isBuffered = false; @@ -35,10 +34,6 @@ public static IEnumerable AllAndLast(this IEnumerable en buffer = item; } - if (isBuffered) - { - yield return onLast(buffer!); - } + if (isBuffered) yield return onLast(buffer!); } - } \ No newline at end of file diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs index 28bc64a2..9de3e02d 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs @@ -1,9 +1,11 @@ using Microsoft.CodeAnalysis; + namespace DynamoDBGenerator.SourceGenerator.Extensions; public static class NotNullEvaluationExtensions { - public static string NotNullTernaryExpression(this ITypeSymbol typeSymbol, in string accessPattern, in string truthy, + public static string NotNullTernaryExpression(this ITypeSymbol typeSymbol, in string accessPattern, + in string truthy, in string falsy) { var result = Expression(in typeSymbol, in accessPattern) is { } expression @@ -18,47 +20,51 @@ private static string CreateException(in string accessPattern) return $"throw {Constants.DynamoDBGenerator.ExceptionHelper.NullExceptionMethod}(nameof({accessPattern}));"; } - public static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbol, string accessPattern, string truthy) + public static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbol, string accessPattern, + string truthy) { return NotNullIfStatement(typeSymbol, accessPattern, obj: truthy); } - public static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbol, string accessPattern, IEnumerable truthy) + + public static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbol, string accessPattern, + IEnumerable truthy) { return NotNullIfStatement(typeSymbol, accessPattern, obj: truthy); } - + private static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbol, string accessPattern, object obj) { if (Expression(typeSymbol, accessPattern) is not { } expression) { - if(obj is string single) - yield return single; - else if(obj is IEnumerable truthies) - foreach (var x in truthies) - yield return x; - else - throw new NotImplementedException($"Method '{nameof(NotNullIfStatement)}' could not determine type '{obj.GetType().Name}'"); + if (obj is string single) + yield return single; + else if (obj is IEnumerable truthies) + foreach (var x in truthies) + yield return x; + else + throw new NotImplementedException( + $"Method '{nameof(NotNullIfStatement)}' could not determine type '{obj.GetType().Name}'"); } else { - - var ifClause = obj switch + var ifClause = obj switch { - string single => $"if ({expression})".CreateScope(single), - IEnumerable multiple => $"if ({expression})".CreateScope(multiple), - _ => throw new NotImplementedException($"Method '{nameof(NotNullIfStatement)}' could not determine type '{obj.GetType().Name}'") + string single => $"if ({expression})".CreateScope(single), + IEnumerable multiple => $"if ({expression})".CreateScope(multiple), + _ => throw new NotImplementedException( + $"Method '{nameof(NotNullIfStatement)}' could not determine type '{obj.GetType().Name}'") }; var enumerable = typeSymbol.NullableAnnotation switch { NullableAnnotation.None or NullableAnnotation.Annotated => ifClause, - NullableAnnotation.NotAnnotated => ifClause.Concat("else".CreateScope(CreateException(in accessPattern))), + NullableAnnotation.NotAnnotated => ifClause.Concat( + "else".CreateScope(CreateException(in accessPattern))), _ => throw new ArgumentOutOfRangeException(typeSymbol.ToDisplayString()) }; foreach (var element in enumerable) yield return element; } - } @@ -73,7 +79,6 @@ private static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbo static string? OnValueType(in ITypeSymbol typeSymbol, in string accessPattern) { - if (typeSymbol.TryGetNullableValueType() is not { } namedTypeSymbol) return null; @@ -86,6 +91,4 @@ private static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbo : $"{accessPattern} is not null && {expression}"; } } - - -} +} \ No newline at end of file diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs index 8685a1f9..913bdbf2 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs @@ -1,18 +1,21 @@ +using System.Runtime.CompilerServices; + namespace DynamoDBGenerator.SourceGenerator.Extensions; public static class StringExtensions { - public static string ToCamelCaseFromPascal(this string str, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) + public static string ToCamelCaseFromPascal(this string str, [CallerMemberName] string? memberName = null) { return ToCamelCaseFromPascal(str.AsSpan(), memberName).ToString(); } - public static string ToPrivateFieldFromPascal(this string str, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) + public static string ToPrivateFieldFromPascal(this string str, [CallerMemberName] string? memberName = null) { return ToPrivateFieldFromPascal(str.AsSpan(), memberName).ToString(); } - public static ReadOnlySpan ToPrivateFieldFromPascal(this ReadOnlySpan span, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) + public static ReadOnlySpan ToPrivateFieldFromPascal(this ReadOnlySpan span, + [CallerMemberName] string? memberName = null) { if (span.Length is 0) throw new ArgumentException($"Null or Empty string was provided from '{memberName}'"); @@ -20,7 +23,7 @@ public static ReadOnlySpan ToPrivateFieldFromPascal(this ReadOnlySpan ToPrivateFieldFromPascal(this ReadOnlySpan ToCamelCaseFromPascal(this ReadOnlySpan span, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) + + public static ReadOnlySpan ToCamelCaseFromPascal(this ReadOnlySpan span, + [CallerMemberName] string? memberName = null) { if (span.Length is 0) throw new ArgumentException($"Null or Empty string was provided from '{memberName}'"); @@ -38,7 +43,7 @@ public static ReadOnlySpan ToCamelCaseFromPascal(this ReadOnlySpan s var array = new char[span.Length]; - array[0] = Char.ToLowerInvariant(span[0]); + array[0] = char.ToLowerInvariant(span[0]); // Skip first element since we handled it manually. for (var i = 1; i < span.Length; i++) @@ -92,12 +97,9 @@ public static string ToAlphaNumericMethodName(this string txt) for (var i = 0; i < txt.Length; i++) { var c = txt[i]; - if (char.IsLetter(c) || index > 0 && char.IsNumber(c)) - { - arr[index++] = c; - } + if (char.IsLetter(c) || (index > 0 && char.IsNumber(c))) arr[index++] = c; } return new string(arr, 0, index); } -} +} \ No newline at end of file diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/TypeExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/TypeExtensions.cs index ccc15b49..d9898253 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/TypeExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/TypeExtensions.cs @@ -1,6 +1,4 @@ -using System.Collections; using System.Collections.Concurrent; -using System.Collections.Immutable; using DynamoDBGenerator.SourceGenerator.Types; using Microsoft.CodeAnalysis; @@ -8,6 +6,13 @@ namespace DynamoDBGenerator.SourceGenerator.Extensions; public static class TypeExtensions { + private static readonly Dictionary TypeIdentifierDictionary = + new(SymbolEqualityComparer.IncludeNullability); + + private static readonly SymbolDisplayFormat DisplayFormat = new( + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters + ); + public static IEnumerable NamespaceDeclaration(this INamedTypeSymbol type, IEnumerable content) { return type.ContainingNamespace.IsGlobalNamespace @@ -50,25 +55,6 @@ public static Func CacheFactory(IEqualityComparer co return x => cache.TryGetValue(x, out var value) ? value : cache[x] = selector(x); } - - private static readonly Dictionary TypeIdentifierDictionary = - new(SymbolEqualityComparer.IncludeNullability); - - public static bool IsNullable(this ITypeSymbol typeSymbol) - { - return typeSymbol switch - { - { - IsReferenceType: true, NullableAnnotation: NullableAnnotation.None or NullableAnnotation.Annotated - } => true, - { IsReferenceType: true, NullableAnnotation: NullableAnnotation.NotAnnotated } => false, - { IsValueType: true, OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } => true, - { IsValueType: true } => false, - _ => throw new ArgumentOutOfRangeException( - $"Could not determine nullablity of type '{typeSymbol.ToDisplayString()}'.") - }; - } - public static TypeIdentifier TypeIdentifier(this ITypeSymbol type) { if (TypeIdentifierDictionary.TryGetValue(type, out var typeIdentifier)) @@ -88,112 +74,14 @@ static TypeIdentifier Create(ITypeSymbol typeSymbol) } } - private static readonly ConcurrentDictionary RepresentationDictionary = - new(SymbolEqualityComparer.IncludeNullability); - - public static (string annotated, string original) Representation(this ITypeSymbol typeSymbol) - { - return RepresentationDictionary.GetOrAdd(typeSymbol, x => - { - var displayString = x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return RepresentationDictionary[typeSymbol] = (ToString(typeSymbol, displayString), displayString); - }); - - static string ToString(ITypeSymbol x, string displayString) - { - if (x is IArrayTypeSymbol arrayTypeSymbol) - { - var result = ToString(arrayTypeSymbol.ElementType, - arrayTypeSymbol.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - - return x.NullableAnnotation switch - { - NullableAnnotation.Annotated or NullableAnnotation.None => $"{result}[]?", - NullableAnnotation.NotAnnotated => $"{result}[]", - _ => throw new ArgumentException(ExceptionMessage(x)) - }; - } - - if (x is not INamedTypeSymbol namedTypeSymbol || namedTypeSymbol.TypeArguments.Length is 0) - { - return x.NullableAnnotation switch - { - // Having `Annotated` and `None` produce append '?' is fine as long as `SuffixedTypeSymbolNameFactory` is giving them different names. Otherwise we could create broken signatures due to duplication. - NullableAnnotation.Annotated or NullableAnnotation.None => $"{displayString}?", - NullableAnnotation.NotAnnotated => displayString, - _ => throw new ArgumentException(ExceptionMessage(x)) - }; - } - - if (namedTypeSymbol.OriginalDefinition.SpecialType is SpecialType.System_Nullable_T) - return displayString; - - if (namedTypeSymbol.IsTupleType) - { - var tupleElements = namedTypeSymbol.TupleElements - .Select(y => - $"{ToString(y.Type, $"{y.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}")} {y.Name}"); - return $"({string.Join(", ", tupleElements)})"; - } - - var index = displayString.AsSpan().IndexOf('<'); - if (index == -1) - return displayString; - - var typeWithoutGenericParameters = displayString.Substring(0, index); - var typeParameters = string.Join(", ", - namedTypeSymbol.TypeArguments.Select(y => - ToString(y, y.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))); - return namedTypeSymbol.NullableAnnotation switch - { - // Having `Annotated` and `None` produce append '?' is fine as long as `SuffixedTypeSymbolNameFactory` is giving them different names. Otherwise we could create broken signatures due to duplication. - NullableAnnotation.Annotated or NullableAnnotation.None => - $"{typeWithoutGenericParameters}<{typeParameters}>?", - NullableAnnotation.NotAnnotated => $"{typeWithoutGenericParameters}<{typeParameters}>", - _ => throw new ArgumentException(ExceptionMessage(namedTypeSymbol)) - }; - - static string ExceptionMessage(ISymbol typeSymbol) => - $"Could nullable annotation on type: {typeSymbol.ToDisplayString()}"; - } - } - - //Source: https://referencesource.microsoft.com/#mscorlib/system/tuple.cs,49b112811bc359fd,references - private class TupleComparer : IEqualityComparer<(ITypeSymbol, ITypeSymbol)> - { - private readonly IEqualityComparer _comparer; - - public TupleComparer(IEqualityComparer comparer) - { - _comparer = comparer; - } - - public bool Equals((ITypeSymbol, ITypeSymbol) x, (ITypeSymbol, ITypeSymbol) y) - { - return _comparer.Equals(x.Item1, y.Item1) && _comparer.Equals(x.Item2, y.Item2); - } - - public int GetHashCode((ITypeSymbol, ITypeSymbol) obj) - { - var hashCode1 = obj.Item1 is null ? 0 : _comparer.GetHashCode(obj.Item1); - var hashCode2 = obj.Item2 is null ? 0 : _comparer.GetHashCode(obj.Item2); - return CombineHashCodes(hashCode1, hashCode2); - } - - private static int CombineHashCodes(int h1, int h2) - { - return ((h1 << 5) + h1) ^ h2; - } - } public static Func SuffixedFullyQualifiedTypeName(string suffix, IEqualityComparer comparer) { IEqualityComparer<(ITypeSymbol, ITypeSymbol)> tupleComparer = new TupleComparer(comparer); var dict = new ConcurrentDictionary<(ITypeSymbol, ITypeSymbol), string>(tupleComparer); if (Equals(comparer, SymbolEqualityComparer.Default)) - { return (x, y) => dict.GetOrAdd( - (x,y), + (x, y), tuple => { var (p, c) = tuple; @@ -201,7 +89,6 @@ public static Func SuffixedFullyQualifiedTypeN ? $"{p.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{c.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}_{c.BaseType!.ToDisplayString()}{suffix}" : $"{p.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{c.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).ToAlphaNumericMethodName()}{suffix}"; }); - } throw new NotSupportedException( $"Method {nameof(SuffixedFullyQualifiedTypeName)} does not implement equality comparer '{comparer.GetType().Name}" @@ -223,8 +110,8 @@ public static Func SuffixedTypeSymbolNameFactory(string? su return x => dict.GetOrAdd(x, y => y is IArrayTypeSymbol ? $"{y.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}_{y.BaseType!.ToDisplayString()}" - : y.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).ToAlphaNumericMethodName() - ); + : y.ToDisplayString( + DisplayFormat).ToAlphaNumericMethodName()); return x => dict.GetOrAdd(x, y => y is IArrayTypeSymbol @@ -273,16 +160,18 @@ when string.Join("_", namedTypeSymbol.TypeArguments.Select(NullableAnnotation)) _ => throw new NotImplementedException(ExceptionMessage(single)) }, { NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.NotAnnotated } => - $"NN_{x.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).ToAlphaNumericMethodName()}", + $"NN_{x.ToDisplayString(DisplayFormat).ToAlphaNumericMethodName()}", { NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.None } => - $"{x.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).ToAlphaNumericMethodName()}", + $"{x.ToDisplayString(DisplayFormat).ToAlphaNumericMethodName()}", { NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.Annotated } => - $"N_{x.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).ToAlphaNumericMethodName()}", + $"N_{x.ToDisplayString(DisplayFormat).ToAlphaNumericMethodName()}", _ => throw new NotImplementedException(ExceptionMessage(x)) }; - static string ExceptionMessage(ITypeSymbol typeSymbol) => - $"Could not apply naming suffix on type: {typeSymbol.ToDisplayString()}"; + static string ExceptionMessage(ITypeSymbol typeSymbol) + { + return $"Could not apply naming suffix on type: {typeSymbol.ToDisplayString()}"; + } } } @@ -291,9 +180,9 @@ public static CodeFactory ToConversion(this IEnumerable enumerable) return new CodeFactory(enumerable); } - public static CodeFactory ToConversion(this IEnumerable enumerable, ITypeSymbol typeSymbol) + public static CodeFactory ToConversion(this IEnumerable enumerable, TypeIdentifier typeIdentifier) { - return new CodeFactory(enumerable, new[] { typeSymbol }); + return new CodeFactory(enumerable, new[] { typeIdentifier }); } public static INamedTypeSymbol? TryGetNullableValueType(this ITypeSymbol type) @@ -306,22 +195,6 @@ public static CodeFactory ToConversion(this IEnumerable enumerable, ITyp : null; } - public static bool IsNumeric(this ITypeSymbol typeSymbol) - { - return typeSymbol.SpecialType - is SpecialType.System_Int16 - or SpecialType.System_Byte - or SpecialType.System_Int32 - or SpecialType.System_Int64 - or SpecialType.System_SByte - or SpecialType.System_UInt16 - or SpecialType.System_UInt32 - or SpecialType.System_UInt64 - or SpecialType.System_Decimal - or SpecialType.System_Double - or SpecialType.System_Single; - } - public static DynamoDbDataMember[] GetDynamoDbProperties(this ITypeSymbol symbol) { // A special rule when it comes to Tuples. @@ -378,4 +251,33 @@ static IEnumerable Iterator(ITypeSymbol typeSymbol) } while (namedTypeSymbol is { SpecialType: not SpecialType.System_Object }); } } + + + //Source: https://referencesource.microsoft.com/#mscorlib/system/tuple.cs,49b112811bc359fd,references + private class TupleComparer : IEqualityComparer<(ITypeSymbol, ITypeSymbol)> + { + private readonly IEqualityComparer _comparer; + + public TupleComparer(IEqualityComparer comparer) + { + _comparer = comparer; + } + + public bool Equals((ITypeSymbol, ITypeSymbol) x, (ITypeSymbol, ITypeSymbol) y) + { + return _comparer.Equals(x.Item1, y.Item1) && _comparer.Equals(x.Item2, y.Item2); + } + + public int GetHashCode((ITypeSymbol, ITypeSymbol) obj) + { + var hashCode1 = obj.Item1 is null ? 0 : _comparer.GetHashCode(obj.Item1); + var hashCode2 = obj.Item2 is null ? 0 : _comparer.GetHashCode(obj.Item2); + return CombineHashCodes(hashCode1, hashCode2); + } + + private static int CombineHashCodes(int h1, int h2) + { + return ((h1 << 5) + h1) ^ h2; + } + } } \ No newline at end of file diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs index a484a14f..c3379c2d 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs @@ -21,7 +21,7 @@ internal static IEnumerable CreateClasses(DynamoDBMarshallerArguments[] Func getDynamoDbProperties, MarshallerOptions options) { // Using _comparer can double classes when there's a None nullable property mixed with a nullable property - var hashSet = new HashSet(SymbolEqualityComparer.Default); + var hashSet = new HashSet(TypeIdentifier.Default); return arguments .SelectMany(x => @@ -40,7 +40,7 @@ private static IEnumerable TypeContent( var ternaryExpressionName = $"{ConstructorAttributeName} is null ? {@$"""#{x.DDB.AttributeName}"""}: {@$"$""{{{ConstructorAttributeName}}}.#{x.DDB.AttributeName}"""}"; return x.IsUnknown - ? $"{x.DDB.DataMember.NameAsPrivateField} = new (() => new {x.AttributeReference}({ternaryExpressionName}, {ConstructorSetName}));" + ? $"{x.DDB.DataMember.NameAsPrivateField} = new (() => new ({ternaryExpressionName}, {ConstructorSetName}));" : $"{x.DDB.DataMember.NameAsPrivateField} = new (() => {ternaryExpressionName});"; }) .Append($"{SetFieldName} = {ConstructorSetName};") @@ -74,7 +74,7 @@ private static IEnumerable TypeContent( var yields = dataMembers .SelectMany(YieldSelector) - .Append($@"if ({self}.IsValueCreated) yield return new ({self}.Value, ""{typeSymbol.Name}"");"); + .Concat($"if ({self}.IsValueCreated)".CreateScope(@$"yield return new ({self}.Value, ""{typeSymbol.Name}"");")); foreach (var s in $"IEnumerable> {AttributeExpressionNameTrackerInterface}.{AttributeExpressionNameTrackerInterfaceAccessedNames}()" @@ -105,26 +105,25 @@ private static IEnumerable YieldSelector( .CreateScope($"yield return {camelCase};"); } - private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func fn, + private static CodeFactory CreateStruct(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { - var dataMembers = fn(typeSymbol) + var dataMembers = fn(typeIdentifier.TypeSymbol) .Select(x => ( - IsUnknown: !options.IsConvertable(x.DataMember.Type) && - x.DataMember.Type.TypeIdentifier() is UnknownType, + IsUnknown: options.IsUnknown(x.DataMember.TypeIdentifier), DDB: x, DbRef: $"#{x.AttributeName}", - AttributeReference: TypeName(x.DataMember.Type), + AttributeReference: TypeName(x.DataMember.TypeIdentifier.TypeSymbol), AttributeInterfaceName: AttributeExpressionNameTrackerInterface )) .ToArray(); - var structName = TypeName(typeSymbol); + var structName = TypeName(typeIdentifier.TypeSymbol); var @class = $"public readonly struct {structName} : {AttributeExpressionNameTrackerInterface}".CreateScope( - TypeContent(typeSymbol, dataMembers, structName)); - return new CodeFactory(@class, dataMembers.Where(x => x.IsUnknown).Select(x => x.DDB.DataMember.Type)); + TypeContent(typeIdentifier.TypeSymbol, dataMembers, structName)); + return new CodeFactory(@class, dataMembers.Where(x => x.IsUnknown).Select(x => x.DDB.DataMember.TypeIdentifier)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs index 33f6d8d7..d202c0a0 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs @@ -20,7 +20,7 @@ public static class AttributeExpressionValue private const string ValueProvider = "valueIdProvider"; private static IEnumerable TypeContents( - ITypeSymbol typeSymbol, + TypeIdentifier typeIdentifier, (bool IsUnknown, DynamoDbDataMember DDB, string AttributeReference, string AttributeInterfaceName)[] dataMembers, string structName, @@ -31,7 +31,7 @@ MarshallerOptions options const string self = "_this"; var constructorFieldAssignments = dataMembers .Select(x => x.IsUnknown - ? $"{x.DDB.DataMember.NameAsPrivateField} = new (() => new {x.AttributeReference}({ValueProvider}, {MarshallerOptions.ParamReference}));" + ? $"{x.DDB.DataMember.NameAsPrivateField} = new (() => new ({ValueProvider}, {MarshallerOptions.ParamReference}));" : $"{x.DDB.DataMember.NameAsPrivateField} = new ({ValueProvider});") .Append($"{self} = new({ValueProvider});") .Append($"{MarshallerOptions.FieldReference} = {MarshallerOptions.ParamReference};"); @@ -61,21 +61,25 @@ MarshallerOptions options const string param = "entity"; - var yields = (typeSymbol switch + var yields = (typeIdentifier switch { - _ when typeSymbol.IsNullable() => $"if ({param} is null)".CreateScope( + { IsNullable: true } => $"if ({param} is null)".CreateScope( $"yield return new ({self}.Value, {AttributeValueUtilityFactory.Null});", "yield break;"), - { IsReferenceType: true } => $"if ({param} is null)".CreateScope( + { TypeSymbol.IsReferenceType: true } => $"if ({param} is null)".CreateScope( $"throw {ExceptionHelper.NullExceptionMethod}(\"{structName}\");"), _ => Enumerable.Empty() }) .Concat(dataMembers .SelectMany(x => YieldSelector(x, options)) - .Append( - $"if ({self}.IsValueCreated) yield return new ({self}.Value, {Marshaller.InvokeMarshallerMethod(typeSymbol, "entity", $"\"{structName}\"", options, MarshallerOptions.FieldReference)} ?? {AttributeValueUtilityFactory.Null});") + .Concat( + $"if ({self}.IsValueCreated)" + .CreateScope( + $"yield return new ({self}.Value, {Marshaller.InvokeMarshallerMethod(typeIdentifier, "entity", $"\"{structName}\"", options, MarshallerOptions.FieldReference)}{HandeNullability(typeIdentifier)});" + ) + ) ) .ScopeTo( - $"IEnumerable> {interfaceName}.{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({typeSymbol.Representation().annotated} entity)"); + $"IEnumerable> {interfaceName}.{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({typeIdentifier.AnnotatedString} entity)"); foreach (var yield in yields) @@ -84,6 +88,9 @@ _ when typeSymbol.IsNullable() => $"if ({param} is null)".CreateScope( yield return $"public override string ToString() => {self}.Value;"; } + private static string? HandeNullability(TypeIdentifier typeSymbol) => + typeSymbol.IsNullable ? $" ?? {AttributeValueUtilityFactory.Null}" : null; + private static IEnumerable YieldSelector( (bool IsUnknown, DynamoDbDataMember DDB, string AttributeReference, string AttributeInterfaceName) x, MarshallerOptions options) @@ -92,7 +99,7 @@ private static IEnumerable YieldSelector( if (x.IsUnknown) { - return x.DDB.DataMember.Type.NotNullIfStatement( + return x.DDB.DataMember.TypeIdentifier.TypeSymbol.NotNullIfStatement( accessPattern, $"foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({accessPattern}))" .CreateScope("yield return x;") @@ -100,53 +107,51 @@ private static IEnumerable YieldSelector( .ScopeTo($"if ({x.DDB.DataMember.NameAsPrivateField}.IsValueCreated)"); } + return $"if ({x.DDB.DataMember.NameAsPrivateField}.IsValueCreated)".CreateScope( - x.DDB.DataMember.Type.NotNullIfStatement(accessPattern, - $"yield return new ({x.DDB.DataMember.NameAsPrivateField}.Value, {Marshaller.InvokeMarshallerMethod(x.DDB.DataMember.Type, $"entity.{x.DDB.DataMember.Name}", $"\"{x.DDB.DataMember.Name}\"", options, MarshallerOptions.FieldReference)} ?? {AttributeValueUtilityFactory.Null});")); + x.DDB.DataMember.TypeIdentifier.TypeSymbol.NotNullIfStatement( + accessPattern, + $"yield return new ({x.DDB.DataMember.NameAsPrivateField}.Value, {Marshaller.InvokeMarshallerMethod(x.DDB.DataMember.TypeIdentifier, $"entity.{x.DDB.DataMember.Name}", $"\"{x.DDB.DataMember.Name}\"", options, MarshallerOptions.FieldReference)}{HandeNullability(x.DDB.DataMember.TypeIdentifier)});" + )); } internal static IEnumerable CreateClasses(DynamoDBMarshallerArguments[] arguments, Func getDynamoDbProperties, MarshallerOptions options) { // Using _comparer can double classes when there's a None nullable property mixed with a nullable property - var hashSet = new HashSet(SymbolEqualityComparer.Default); + var hashSet = new HashSet(TypeIdentifier.Default); return arguments .SelectMany(x => CodeFactory.Create(x.ArgumentType, y => CreateStruct(y, getDynamoDbProperties, options), hashSet)); } - private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func fn, + private static CodeFactory CreateStruct(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { - var dataMembers = - options.IsConvertable(typeSymbol) - ? Array - .Empty<(bool IsUnknown, DynamoDbDataMember DDB, string AttributeReference, string - AttributeInterfaceName)>() - : fn(typeSymbol) - .Select(x => - { - return ( - IsUnknown: !options.IsConvertable(x.DataMember.Type) && - x.DataMember.Type.TypeIdentifier() is UnknownType, - DDB: x, - AttributeReference: TypeName(x.DataMember.Type), - AttributeInterfaceName: - $"{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerInterface}<{x.DataMember.Type.Representation().annotated}>" - ); - }) - .ToArray(); - - var structName = TypeName(typeSymbol); + var dataMembers = options.IsConvertable(typeIdentifier) + ? Array.Empty<(bool IsUnknown, DynamoDbDataMember DDB, string AttributeReference, string AttributeInterfaceName)>() + : fn(typeIdentifier.TypeSymbol) + .Select(x => ( + IsUnknown: options.IsUnknown(x.DataMember.TypeIdentifier), + DDB: x, + AttributeReference: TypeName(x.DataMember.TypeIdentifier.TypeSymbol), + AttributeInterfaceName: + $"{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerInterface}<{x.DataMember.TypeIdentifier.AnnotatedString}>" + )) + .ToArray(); + + var structName = TypeName(typeIdentifier.TypeSymbol); var interfaceName = - $"{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerInterface}<{typeSymbol.Representation().annotated}>"; + $"{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerInterface}<{typeIdentifier.AnnotatedString}>"; var @struct = - $"public readonly struct {structName} : {interfaceName}".CreateScope(TypeContents(typeSymbol, dataMembers, + $"public readonly struct {structName} : {interfaceName}".CreateScope(TypeContents(typeIdentifier, + dataMembers, structName, interfaceName, options)); - return new CodeFactory(@struct, dataMembers.Where(x => x.IsUnknown).Select(x => x.DDB.DataMember.Type)); + return new CodeFactory(@struct, + dataMembers.Where(x => x.IsUnknown).Select(x => x.DDB.DataMember.TypeIdentifier)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Keys.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Keys.cs index 633bf610..a0dca750 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Keys.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Keys.cs @@ -14,7 +14,7 @@ internal static class KeyMarshaller private const string EnforceRkReference = "isRangeKey"; private static readonly Func MethodName = - TypeExtensions.SuffixedTypeSymbolNameFactory("Keys", SymbolEqualityComparer.IncludeNullability); + TypeExtensions.SuffixedTypeSymbolNameFactory(null, SymbolEqualityComparer.IncludeNullability); private const string PkReference = "partitionKey"; private const string RkReference = "rangeKey"; @@ -23,16 +23,15 @@ private static IEnumerable CreateAssignment(string validateReference, st DynamoDbDataMember dataMember, MarshallerOptions options) { const string reference = "value"; - var expectedType = dataMember.DataMember.Type.Representation().original; - var expression = $"{keyReference} is {expectedType} {{ }} {reference}"; + var expression = $"{keyReference} is {dataMember.DataMember.TypeIdentifier.UnannotatedString} {{ }} {reference}"; var innerContent = $"if ({expression}) " .CreateScope( - $@"{DictionaryName}.Add(""{dataMember.AttributeName}"", {InvokeMarshallerMethod(dataMember.DataMember.Type, reference, $"nameof({keyReference})", options)});") + $@"{DictionaryName}.Add(""{dataMember.AttributeName}"", {InvokeMarshallerMethod(dataMember.DataMember.TypeIdentifier, reference, $"nameof({keyReference})", options)});") .Concat($"else if ({keyReference} is null) ".CreateScope( $@"throw {ExceptionHelper.KeysArgumentNullExceptionMethod}(""{dataMember.DataMember.Name}"", ""{keyReference}"");")) .Concat("else".CreateScope( - $@"throw {ExceptionHelper.KeysInvalidConversionExceptionMethod}(""{dataMember.DataMember.Name}"", ""{keyReference}"", {keyReference}, ""{expectedType}"");")); + $@"throw {ExceptionHelper.KeysInvalidConversionExceptionMethod}(""{dataMember.DataMember.Name}"", ""{keyReference}"", {keyReference}, ""{dataMember.DataMember.TypeIdentifier.UnannotatedString}"");")); return $"if ({validateReference})".CreateScope(innerContent); } @@ -76,7 +75,7 @@ private static IEnumerable MethodBody(ITypeSymbol typeSymbol, internal static IEnumerable CreateKeys(IEnumerable arguments, Func getDynamoDbProperties, MarshallerOptions options) { - var hashSet = new HashSet(SymbolEqualityComparer.IncludeNullability); + var hashSet = new HashSet(TypeIdentifier.Nullable); return arguments .SelectMany(x => CodeFactory.Create(x.EntityTypeSymbol, @@ -148,12 +147,12 @@ internal static string AssignmentRoot(ITypeSymbol typeSymbol) $"new {KeyMarshallerImplementationTypeName}((pk, rk, ipk, irk, dm) => {ClassName}.{MethodName(typeSymbol)}({MarshallerOptions.FieldReference}, pk, rk, ipk, irk, dm))"; } - private static CodeFactory StaticAttributeValueDictionaryKeys(ITypeSymbol typeSymbol, + private static CodeFactory StaticAttributeValueDictionaryKeys(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { var code = - $"public static Dictionary {MethodName(typeSymbol)}({options.FullName} {MarshallerOptions.ParamReference}, object? {PkReference}, object? {RkReference}, bool {EnforcePkReference}, bool {EnforceRkReference}, string? index = null)" - .CreateScope(MethodBody(typeSymbol, fn, options)); + $"public static Dictionary {MethodName(typeIdentifier.TypeSymbol)}({options.FullName} {MarshallerOptions.ParamReference}, object? {PkReference}, object? {RkReference}, bool {EnforcePkReference}, bool {EnforceRkReference}, string? index = null)" + .CreateScope(MethodBody(typeIdentifier.TypeSymbol, fn, options)); return new CodeFactory(code); } diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Marshaller.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Marshaller.cs index 119d4c63..e3424195 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Marshaller.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Marshaller.cs @@ -9,40 +9,40 @@ internal static partial class Marshaller private const string ClassName = $"_{Constants.DynamoDBGenerator.Marshaller.MarshallMethodName}_"; private const string DataMember = "dataMember"; private const string DictionaryReference = "attributeValues"; - private static readonly Func GetSerializationMethodName = TypeExtensions.SuffixedTypeSymbolNameFactory("_M", SymbolEqualityComparer.IncludeNullability); + private static readonly Func GetSerializationMethodName = TypeExtensions.SuffixedTypeSymbolNameFactory(null, SymbolEqualityComparer.IncludeNullability); private const string ParamReference = "entity"; internal static IEnumerable CreateClass(DynamoDBMarshallerArguments[] arguments, Func getDynamoDbProperties, MarshallerOptions options) { return $"file static class {ClassName}".CreateScope(TypeContent(arguments, getDynamoDbProperties, options)); } - private static CodeFactory CreateDictionaryMethod(ITypeSymbol typeSymbol, Func fn, MarshallerOptions options) + private static CodeFactory CreateDictionaryMethod(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { - var properties = fn(typeSymbol) + var properties = fn(typeIdentifier.TypeSymbol) .Select(x => { var accessPattern = $"{ParamReference}.{x.DataMember.Name}"; - var isNullable = x.DataMember.Type.IsNullable(); - var marshallerInvocation = InvokeMarshallerMethod(x.DataMember.Type, accessPattern, $"\"{x.DataMember.Name}\"", options); - var assignment = isNullable - ? $"if ({marshallerInvocation} is {{ }} {x.DataMember.Name})" - .CreateScope($"{DictionaryReference}.Add(\"{x.AttributeName}\", {x.DataMember.Name});" ) - : new[] { $"{DictionaryReference}.Add(\"{x.AttributeName}\", {marshallerInvocation});"}; + var marshallerInvocation = InvokeMarshallerMethod(x.DataMember.TypeIdentifier, accessPattern, $"\"{x.DataMember.Name}\"", options); + + var assignment = x.DataMember.TypeIdentifier.IsNullable + ? $"if ({x.DataMember.NameAsCamelCase} is not null)" + .CreateScope($"{DictionaryReference}[\"{x.AttributeName}\"] = {x.DataMember.NameAsCamelCase};") + .Prepend($"var {x.DataMember.NameAsCamelCase} = {marshallerInvocation};") + : new[] { $"{DictionaryReference}[\"{x.AttributeName}\"] = {marshallerInvocation};" }; return ( dictionaryAssignment: assignment, - capacityTernary: isNullable ? x.DataMember.Type.NotNullTernaryExpression(in accessPattern, "1", "0") : "1", - x.DataMember.Type + capacityTernary: x.DataMember.TypeIdentifier.IsNullable ? x.DataMember.TypeIdentifier.TypeSymbol.NotNullTernaryExpression(in accessPattern, "1", "0") : "1", + x.DataMember ); }) .ToArray(); - var isNullable = typeSymbol.IsNullable(); var enumerable = Enumerable.Empty(); - if (isNullable) + if (typeIdentifier.IsNullable) enumerable = $"if ({ParamReference} is null)".CreateScope("return null;"); - else if (typeSymbol.IsReferenceType) + else if (typeIdentifier.TypeSymbol.IsReferenceType) enumerable = $"if ({ParamReference} is null)".CreateScope($"throw {ExceptionHelper.NullExceptionMethod}({DataMember});"); var body = @@ -51,16 +51,16 @@ private static CodeFactory CreateDictionaryMethod(ITypeSymbol typeSymbol, Func{(isNullable ? '?' : null)} {GetSerializationMethodName(typeSymbol)}({typeSymbol.Representation().annotated} {ParamReference}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)" + $"public static Dictionary{(typeIdentifier.IsNullable ? '?' : null)} {GetSerializationMethodName(typeIdentifier.TypeSymbol)}({typeIdentifier.AnnotatedString} {ParamReference}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)" .CreateScope(body); - return new CodeFactory(code, properties.Select(y => y.Type)); + return new CodeFactory(code, properties.Select(y => y.DataMember.TypeIdentifier)); } private static IEnumerable TypeContent(DynamoDBMarshallerArguments[] arguments, Func getDynamoDbProperties, MarshallerOptions options) { - var hashset = new HashSet(SymbolEqualityComparer.IncludeNullability); + var hashset = new HashSet(TypeIdentifier.Nullable); return arguments.SelectMany(x => CodeFactory .Create( @@ -73,129 +73,154 @@ private static IEnumerable TypeContent(DynamoDBMarshallerArguments[] arg ) .Concat(KeyMarshaller.CreateKeys(arguments, getDynamoDbProperties, options)); } - private static CodeFactory CreateMethod(ITypeSymbol type, Func fn, MarshallerOptions options) + private static CodeFactory CreateMethod(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { - if (options.TryWriteConversion(type, ParamReference) is {} conversion) + if (options.TryWriteConversion(typeIdentifier.TypeSymbol, ParamReference) is {} conversion) { - return type switch + return typeIdentifier.TypeSymbol switch { - { IsValueType: true } => type switch + { IsValueType: true } => typeIdentifier.TypeSymbol switch { - { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } => CreateSignature(type, options) - .CreateScope($"return {ParamReference} is not null ? {conversion} : null;") + { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } => CreateSignature(typeIdentifier, options) + .CreateScope($"if ({ParamReference} is null)" + .CreateScope("return null;") + .Append($"return {conversion};") + ) .ToConversion(), - _ => CreateSignature(type, options) + _ => CreateSignature(typeIdentifier, options) .CreateScope($"return {conversion};") .ToConversion() }, - { IsReferenceType: true } => type switch + { IsReferenceType: true } => typeIdentifier.TypeSymbol switch { - { NullableAnnotation: NullableAnnotation.None or NullableAnnotation.Annotated } => CreateSignature(type, options) - .CreateScope($"return {ParamReference} is not null ? {conversion} : null;") + { NullableAnnotation: NullableAnnotation.None or NullableAnnotation.Annotated } => CreateSignature(typeIdentifier, options) + .CreateScope($"if ({ParamReference} is null)".CreateScope("return null;") + .Append($"return {conversion};")) .ToConversion(), - _ => CreateSignature(type, options) - .CreateScope($"return {ParamReference} is not null ? {conversion} : throw {ExceptionHelper.NullExceptionMethod}({DataMember});") + _ => CreateSignature(typeIdentifier, options) + .CreateScope( + $"if ({ParamReference} is null)".CreateScope($"throw {ExceptionHelper.NullExceptionMethod}({DataMember});").Append($"return {conversion};") + ) .ToConversion() }, - _ => throw new ArgumentException($"Neither ValueType or ReferenceType could be resolved for conversion. type '{type.ToDisplayString()}'.") + _ => throw new ArgumentException( + $"Neither ValueType or ReferenceType could be resolved for conversion. type '{typeIdentifier.TypeSymbol.ToDisplayString()}'.") }; } - return type.TypeIdentifier() switch + return typeIdentifier switch { - SingleGeneric singleGeneric when CreateSignature(singleGeneric.TypeSymbol, options) is var signature => singleGeneric.Type switch + SingleGeneric singleGeneric when CreateSignature(singleGeneric, options) is var signature => singleGeneric.Type switch { SingleGeneric.SupportedType.Nullable => signature - .CreateScope($"return {ParamReference} is not null ? {InvokeMarshallerMethod(singleGeneric.T, $"{ParamReference}.Value", DataMember, options)} : null;") - .ToConversion(singleGeneric.T), + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {InvokeMarshallerMethod(singleGeneric.T, $"{ParamReference}.Value", DataMember, options)};") + ).ToConversion(singleGeneric.T), SingleGeneric.SupportedType.Array => signature - .CreateScope($"return {ParamReference} is not null ? {AttributeValueUtilityFactory.FromArray}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(singleGeneric.T, "a", "d", options, "o")}{(singleGeneric.T.IsNullable() ? $" ?? {AttributeValueUtilityFactory.Null}" : null)}) : {Else(singleGeneric)};") - .ToConversion(singleGeneric.T), + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.FromArray}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(singleGeneric.T, "a", "d", options, "o")}{(singleGeneric.T.IsNullable ? $" ?? {AttributeValueUtilityFactory.Null}" : null)});") + ).ToConversion(singleGeneric.T), SingleGeneric.SupportedType.List => signature - .CreateScope($"return {ParamReference} is not null ? {AttributeValueUtilityFactory.FromList}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(singleGeneric.T, "a", "d", options, "o")}{(singleGeneric.T.IsNullable() ? $" ?? {AttributeValueUtilityFactory.Null}" : null)}) : {Else(singleGeneric)};") - .ToConversion(singleGeneric.T), + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.FromList}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(singleGeneric.T, "a", "d", options, "o")}{(singleGeneric.T.IsNullable ? $" ?? {AttributeValueUtilityFactory.Null}" : null)});") + ).ToConversion(singleGeneric.T), SingleGeneric.SupportedType.IReadOnlyCollection or SingleGeneric.SupportedType.IEnumerable or SingleGeneric.SupportedType.ICollection => signature - .CreateScope($"return {ParamReference} is not null ? {AttributeValueUtilityFactory.FromEnumerable}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(singleGeneric.T, "a", "d", options, "o")}{(singleGeneric.T.IsNullable() ? $" ?? {AttributeValueUtilityFactory.Null}" : null)}) : {Else(singleGeneric)};") - .ToConversion(singleGeneric.T), - SingleGeneric.SupportedType.Set when singleGeneric.T.SpecialType is SpecialType.System_String + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.FromEnumerable}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(singleGeneric.T, "a", "d", options, "o")}{(singleGeneric.T.IsNullable ? $" ?? {AttributeValueUtilityFactory.Null}" : null)});") + ).ToConversion(singleGeneric.T), + SingleGeneric.SupportedType.Set when singleGeneric.T.TypeSymbol.SpecialType is SpecialType.System_String => signature .CreateScope( - $"return {ParamReference} is not null ? new AttributeValue {{ SS = new List<{(singleGeneric.T.IsNullable() ? "string?" : "string")}>({(singleGeneric.T.IsNullable() ? ParamReference : $"{ParamReference}.Select((y,i) => y ?? throw {ExceptionHelper.NullExceptionMethod}($\"{{{DataMember}}}[UNKNOWN]\"))")})}} : {Else(singleGeneric)};") + $"if ({ParamReference} is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return new {Constants.AWSSDK_DynamoDBv2.AttributeValue} {{ SS = new List<{(singleGeneric.T.IsNullable ? "string?" : "string")}>({(singleGeneric.T.IsNullable ? ParamReference : $"{ParamReference}.Select((y,i) => y ?? throw {ExceptionHelper.NullExceptionMethod}($\"{{{DataMember}}}[UNKNOWN]\"))")})}};") + ) .ToConversion(singleGeneric.T), - SingleGeneric.SupportedType.Set when singleGeneric.T.IsNumeric() + SingleGeneric.SupportedType.Set when singleGeneric.T.IsNumeric => signature - .CreateScope($"return {ParamReference} is not null ? new AttributeValue {{ NS = new List({ParamReference}.Select(y => y.ToString())) }} : {Else(singleGeneric)};") + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return new {Constants.AWSSDK_DynamoDBv2.AttributeValue} {{ NS = new List({ParamReference}.Select(y => y.ToString())) }};") + ) .ToConversion(singleGeneric.T), SingleGeneric.SupportedType.Set => throw new ArgumentException("Only string and integers are supported for sets", UncoveredConversionException(singleGeneric, nameof(CreateMethod))), _ => throw UncoveredConversionException(singleGeneric, nameof(CreateMethod)) }, KeyValueGeneric {TKey.SpecialType: not SpecialType.System_String} keyValueGeneric => throw new ArgumentException("Only strings are supported for for TKey", UncoveredConversionException(keyValueGeneric, nameof(CreateMethod))), - KeyValueGeneric keyValueGeneric when CreateSignature(keyValueGeneric.TypeSymbol, options) is var signature => keyValueGeneric.Type switch + KeyValueGeneric keyValueGeneric when CreateSignature(keyValueGeneric, options) is var signature => keyValueGeneric.Type switch { KeyValueGeneric.SupportedType.Dictionary => signature - .CreateScope($"return {ParamReference} is not null ? {AttributeValueUtilityFactory.FromDictionary}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(keyValueGeneric.TValue, "a", "d", options, "o")}{(keyValueGeneric.TValue.IsNullable() ? $" ?? {AttributeValueUtilityFactory.Null}" : null)}) : {Else(keyValueGeneric)};") + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(keyValueGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.FromDictionary}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(keyValueGeneric.TValue, "a", "d", options, "o")}{(keyValueGeneric.TValue.IsNullable ? $" ?? {AttributeValueUtilityFactory.Null}" : null)});") + ) .ToConversion(keyValueGeneric.TValue), KeyValueGeneric.SupportedType.LookUp => signature - .CreateScope($"return {ParamReference} is not null ? {AttributeValueUtilityFactory.FromLookup}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(keyValueGeneric.TValue, "a", "d", options, "o")}{(keyValueGeneric.TValue.IsNullable() ? $" ?? {AttributeValueUtilityFactory.Null}" : null)}) : {Else(keyValueGeneric)};") + .CreateScope( + $"if ({ParamReference} is null)" + .CreateScope(keyValueGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.FromLookup}({ParamReference}, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeMarshallerMethod(keyValueGeneric.TValue, "a", "d", options, "o")}{(keyValueGeneric.TValue.IsNullable ? $" ?? {AttributeValueUtilityFactory.Null}" : null)});") + ) .ToConversion(keyValueGeneric.TValue), _ => throw UncoveredConversionException(keyValueGeneric, nameof(CreateMethod)) }, - UnknownType unknownType => CreateDictionaryMethod(unknownType.TypeSymbol, fn, options), - var typeIdentifier => throw UncoveredConversionException(typeIdentifier, nameof(CreateMethod)) + UnknownType unknownType => CreateDictionaryMethod(unknownType, fn, options), + _ => throw UncoveredConversionException(typeIdentifier, nameof(CreateMethod)) }; } - private static string CreateSignature(ITypeSymbol typeSymbol, MarshallerOptions options) + private static string CreateSignature(TypeIdentifier typeIdentifier, MarshallerOptions options) { - return typeSymbol.IsNullable() - ? $"public static AttributeValue? {GetSerializationMethodName(typeSymbol)}({typeSymbol.Representation().annotated} {ParamReference}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)" - : $"public static AttributeValue {GetSerializationMethodName(typeSymbol)}({typeSymbol.Representation().annotated} {ParamReference}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)"; + var typeSymbol = typeIdentifier.TypeSymbol; + return typeIdentifier.IsNullable + ? $"public static {Constants.AWSSDK_DynamoDBv2.AttributeValue}? {GetSerializationMethodName(typeSymbol)}({typeIdentifier.AnnotatedString} {ParamReference}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)" + : $"public static {Constants.AWSSDK_DynamoDBv2.AttributeValue} {GetSerializationMethodName(typeSymbol)}({typeIdentifier.AnnotatedString} {ParamReference}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)"; } - private static string Else(TypeIdentifier typeIdentifier) - { - return typeIdentifier.TypeSymbol.IsNullable() ? "null" : $"throw {ExceptionHelper.NullExceptionMethod}({DataMember})"; - } private static IEnumerable InitializeDictionary(IEnumerable capacityCalculations) { var capacityCalculation = string.Join(" + ", capacityCalculations); if (capacityCalculation is "") { - yield return $"var {DictionaryReference} = new Dictionary(0);"; + yield return $"var {DictionaryReference} = new Dictionary(0);"; } else { yield return $"var capacity = {capacityCalculation};"; - yield return $"var {DictionaryReference} = new Dictionary(capacity);"; + yield return $"var {DictionaryReference} = new Dictionary(capacity);"; } } - internal static string InvokeMarshallerMethod(ITypeSymbol typeSymbol, string parameterReference, string dataMember, MarshallerOptions options, string optionParam = MarshallerOptions.ParamReference) + internal static string InvokeMarshallerMethod(TypeIdentifier typeIdentifier, string parameterReference, string dataMember, MarshallerOptions options, string optionParam = MarshallerOptions.ParamReference) { - var invocation = $"{ClassName}.{GetSerializationMethodName(typeSymbol)}({parameterReference}, {optionParam}, {dataMember})"; - - if (options.IsConvertable(typeSymbol)) - return invocation; - - if (typeSymbol.TypeIdentifier() is UnknownType) - return typeSymbol.IsNullable() - ? $"{invocation} switch {{ {{ }} x => new AttributeValue {{ M = x }}, null => null }}" - : $"new AttributeValue {{ M = {invocation} }}"; + var invocation = $"{ClassName}.{GetSerializationMethodName(typeIdentifier.TypeSymbol)}({parameterReference}, {optionParam}, {dataMember})"; - return invocation; + return options.IsUnknown(typeIdentifier) is false + ? invocation + : $"{AttributeValueUtilityFactory.ToAttributeValue}({invocation})"; } - internal static IEnumerable RootSignature(ITypeSymbol typeSymbol, string rootTypeName) + internal static IEnumerable RootSignature(TypeIdentifier typeIdentifier) { - return $"public Dictionary<{nameof(String)}, {Constants.AWSSDK_DynamoDBv2.AttributeValue}> {Constants.DynamoDBGenerator.Marshaller.MarshallMethodName}({rootTypeName} {ParamReference})" + return $"public Dictionary<{nameof(String)}, {Constants.AWSSDK_DynamoDBv2.AttributeValue}> {Constants.DynamoDBGenerator.Marshaller.MarshallMethodName}({typeIdentifier.AnnotatedString} {ParamReference})" .CreateScope( $"ArgumentNullException.ThrowIfNull({ParamReference});", - $"return {ClassName}.{GetSerializationMethodName(typeSymbol)}({ParamReference}, {MarshallerOptions.FieldReference});" + $"return {ClassName}.{GetSerializationMethodName(typeIdentifier.TypeSymbol)}({ParamReference}, {MarshallerOptions.FieldReference});" ); } diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/UnMarshaller.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/UnMarshaller.cs index 04e4b33a..da22b621 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/UnMarshaller.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/UnMarshaller.cs @@ -10,12 +10,13 @@ public static class UnMarshaller { private const string DataMember = "dataMember"; private const string Dict = "dict"; - private static readonly Func GetDeserializationMethodName = TypeExtensions.SuffixedTypeSymbolNameFactory("_U", SymbolEqualityComparer.IncludeNullability); + private static readonly Func GetDeserializationMethodName = TypeExtensions.SuffixedTypeSymbolNameFactory(null, SymbolEqualityComparer.IncludeNullability); private const string UnMarshallerClass = $"_{Marshaller.UnmarshalMethodName}_"; private const string Value = "attributeValue"; - private static IEnumerable<(bool useParentheses, IEnumerable assignments)> Assignments(ITypeSymbol type, (DynamoDbDataMember DDB, string MethodCall, string Name)[] assignments) + private static IEnumerable<(bool useParentheses, IEnumerable assignments)> Assignments(TypeIdentifier typeIdentifier, (DynamoDbDataMember DDB, string MethodCall, string Name)[] assignments) { const string indent = " "; + var type = typeIdentifier.TypeSymbol; if (type.IsTupleType) yield return (true, assignments.Select(x => $"{indent}{x.DDB.DataMember.Name}: {x.MethodCall}")); else @@ -46,99 +47,173 @@ internal static IEnumerable CreateClass(DynamoDBMarshallerArguments[] ar { return $"file static class {UnMarshallerClass}".CreateScope(CreateTypeContents(arguments, getDynamoDbProperties, options)); } - private static CodeFactory CreateCode(ITypeSymbol type, Func fn, MarshallerOptions options) + private static CodeFactory CreateCode(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { - var assignments = fn(type) - .Select(x => (DDB: x, MethodCall: InvokeUnmarshallMethod(x.DataMember.Type, $"{Dict}.GetValueOrDefault(\"{x.AttributeName}\")", $"\"{x.DataMember.Name}\"", options), x.DataMember.Name)) + var assignments = fn(typeIdentifier.TypeSymbol) + .Select(x => (DDB: x, MethodCall: InvokeUnmarshallMethod(x.DataMember.TypeIdentifier, $"{Dict}.GetValueOrDefault(\"{x.AttributeName}\")", $"\"{x.DataMember.Name}\"", options), x.DataMember.Name)) .ToArray(); - var typeName = type.Representation(); var blockBody = $"if ({Dict} is null)" - .CreateScope(type.IsNullable() ? "return null;" : $"throw {ExceptionHelper.NullExceptionMethod}({DataMember});").Concat( - Assignments(type, assignments) + .CreateScope(typeIdentifier.IsNullable ? "return null;" : $"throw {ExceptionHelper.NullExceptionMethod}({DataMember});").Concat( + Assignments(typeIdentifier, assignments) .AllAndLast(x => ObjectAssignmentBlock(x.useParentheses, x.assignments, false), x => ObjectAssignmentBlock(x.useParentheses, x.assignments, true)) .SelectMany(x => x) .DefaultIfEmpty("();") // Is needed in order to not perform new entity? where '?' is not allowed in the end of the string. - .Prepend(type.IsTupleType ? "return" : $"return new {typeName.annotated.TrimEnd('?')}") + .Prepend(typeIdentifier.TypeSymbol.IsTupleType ? "return" : $"return new {typeIdentifier.AnnotatedString.TrimEnd('?')}") ); - var method = $"public static {typeName.annotated} {GetDeserializationMethodName(type)}(Dictionary? {Dict}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)".CreateScope(blockBody); + var method = $"public static {typeIdentifier.AnnotatedString} {GetDeserializationMethodName(typeIdentifier.TypeSymbol)}(Dictionary? {Dict}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)".CreateScope(blockBody); - return new CodeFactory(method, assignments.Select(x => x.DDB.DataMember.Type)); + return new CodeFactory(method, assignments.Select(x => x.DDB.DataMember.TypeIdentifier)); } - private static CodeFactory CreateMethod(ITypeSymbol type, Func fn, + private static CodeFactory CreateMethod(TypeIdentifier typeIdentifier, Func fn, MarshallerOptions options) { - - if (options.TryReadConversion(type, Value) is {} conversion) + if (options.TryReadConversion(typeIdentifier, Value) is {} conversion) { - if (type.IsNullable()) - return CreateSignature(type, options) - .CreateScope($"return {Value} is not null ? ({conversion}) : null;") - .ToConversion(); - - return CreateSignature(type, options) - .CreateScope($"return {Value} is not null && ({conversion}) is {{ }} x ? x : throw {ExceptionHelper.NullExceptionMethod}({DataMember});") - .ToConversion(); + return typeIdentifier.TypeSymbol switch + { + { IsValueType: true } => typeIdentifier.TypeSymbol switch + { + { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } => CreateSignature(typeIdentifier, options) + .CreateScope( + $"if ({Value} is null)" + .CreateScope("return null;") + .Append($"return {conversion};") + ) + .ToConversion(), + _ => CreateSignature(typeIdentifier, options) + .CreateScope( + $"if ({Value} is null)" + .CreateScope($"throw {ExceptionHelper.NullExceptionMethod}({DataMember});") + .Append($"var result = {conversion};") + .Concat( + "if (result is null)" + .CreateScope($"throw {ExceptionHelper.NullExceptionMethod}({DataMember});") + ) + .Append("return result.Value;") + ) + .ToConversion() + + }, + { IsReferenceType: true } => typeIdentifier.TypeSymbol switch + { + { NullableAnnotation: NullableAnnotation.None or NullableAnnotation.Annotated } => CreateSignature( + typeIdentifier, options) + .CreateScope( + $"if ({Value} is null)" + .CreateScope("return null;") + .Append($"return {conversion};") + ).ToConversion(), + _ => CreateSignature(typeIdentifier, options) + .CreateScope( + $"if ({Value} is null)" + .CreateScope($"throw {ExceptionHelper.NullExceptionMethod}({DataMember});") + .Append($"var result = {conversion};") + .Concat( + "if (result is null)" + .CreateScope($"throw {ExceptionHelper.NullExceptionMethod}({DataMember});") + ) + .Append("return result;") + ) + .ToConversion() + + + }, + _ => throw new ArgumentException( + $"Neither ValueType or ReferenceType could be resolved for conversion. type '{typeIdentifier.TypeSymbol.ToDisplayString()}'." + ) + }; } - return type.TypeIdentifier() switch + return typeIdentifier switch { - SingleGeneric singleGeneric when CreateSignature(singleGeneric.TypeSymbol, options) is var signature => singleGeneric.Type switch + SingleGeneric singleGeneric when CreateSignature(singleGeneric, options) is var signature => singleGeneric.Type switch { SingleGeneric.SupportedType.Nullable => signature - .CreateScope($"return {Value} is not null and {{ NULL: false }} ? {InvokeUnmarshallMethod(singleGeneric.T, Value, DataMember, options)} : null;") + .CreateScope( + $"if ({Value} is null || {Value}.NULL is true)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {InvokeUnmarshallMethod(singleGeneric.T, Value, DataMember, options)};" ) + ) .ToConversion(singleGeneric.T), SingleGeneric.SupportedType.List or SingleGeneric.SupportedType.ICollection => signature - .CreateScope($"return {Value} is {{ L: {{ }} x }} ? {AttributeValueUtilityFactory.ToList}(x, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(singleGeneric.T, "a", "d", options, "o")}) : {Else(singleGeneric.TypeSymbol)};") + .CreateScope( + $"if ({Value} is null || {Value}.L is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.ToList}({Value}.L, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(singleGeneric.T, "a", "d", options, "o")});") + ) .ToConversion(singleGeneric.T), SingleGeneric.SupportedType.Array or SingleGeneric.SupportedType.IReadOnlyCollection => signature - .CreateScope($"return {Value} is {{ L: {{ }} x }} ? {AttributeValueUtilityFactory.ToArray}(x, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(singleGeneric.T, "a", "d", options, "o")}) : {Else(singleGeneric.TypeSymbol)};") + .CreateScope( + $"if ({Value} is null || {Value}.L is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.ToArray}({Value}.L, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(singleGeneric.T, "a", "d", options, "o")});") + ) .ToConversion(singleGeneric.T), SingleGeneric.SupportedType.IEnumerable => signature - .CreateScope($"return {Value} is {{ L: {{ }} x }} ? {AttributeValueUtilityFactory.ToEnumerable}(x, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(singleGeneric.T, "a", "d", options, "o")}) : {Else(singleGeneric.TypeSymbol)};") + .CreateScope( + $"if ({Value} is null || {Value}.L is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.ToEnumerable}({Value}.L, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(singleGeneric.T, "a", "d", options, "o")});") + ) .ToConversion(singleGeneric.T), - SingleGeneric.SupportedType.Set when singleGeneric.T.SpecialType is SpecialType.System_String => signature + SingleGeneric.SupportedType.Set when singleGeneric.T.TypeSymbol.SpecialType is SpecialType.System_String => signature .CreateScope( - $"return {Value} is {{ SS : {{ }} x }} ? new {(singleGeneric.TypeSymbol.TypeKind is TypeKind.Interface ? $"HashSet<{(singleGeneric.T.IsNullable() ? "string?" : "string")}>" : null)}({(singleGeneric.T.IsNullable() ? "x" : $"x.Select((y,i) => y ?? throw {ExceptionHelper.NullExceptionMethod}($\"{{{DataMember}}}[UNKNOWN]\")")})) : {Else(singleGeneric.TypeSymbol)};") + $"if ({Value} is null || {Value}.SS is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return new {(singleGeneric.TypeSymbol.TypeKind is TypeKind.Interface ? $"HashSet<{(singleGeneric.T.IsNullable ? "string?" : "string")}>" : null)}({(singleGeneric.T.IsNullable ? $"{Value}.SS" : $"{Value}.SS.Select((y,i) => y ?? throw {ExceptionHelper.NullExceptionMethod}($\"{{{DataMember}}}[UNKNOWN]\")")}));") + ) .ToConversion(), - SingleGeneric.SupportedType.Set when singleGeneric.T.IsNumeric() => signature + SingleGeneric.SupportedType.Set when singleGeneric.T.IsNumeric => signature .CreateScope( - $"return {Value} is {{ NS : {{ }} x }} ? new {(singleGeneric.TypeSymbol.TypeKind is TypeKind.Interface ? $"HashSet<{singleGeneric.T.Representation().original}>" : null)}(x.Select(y => {singleGeneric.T.Representation().original}.Parse(y))) : {Else(singleGeneric.TypeSymbol)};") - .ToConversion(singleGeneric.TypeSymbol), + $"if ({Value} is null || {Value}.NS is null)" + .CreateScope(singleGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return new {(singleGeneric.TypeSymbol.TypeKind is TypeKind.Interface ? $"HashSet<{singleGeneric.T.UnannotatedString}>" : null)}({Value}.NS.Select(y => {singleGeneric.T.UnannotatedString}.Parse(y)));") + ) + .ToConversion(singleGeneric), SingleGeneric.SupportedType.Set => throw new ArgumentException("Only string and integers are supported for sets", UncoveredConversionException(singleGeneric, nameof(CreateMethod))), _ => throw UncoveredConversionException(singleGeneric, nameof(CreateMethod)) }, KeyValueGeneric {TKey.SpecialType: not SpecialType.System_String} keyValueGeneric => throw new ArgumentException("Only strings are supported for for TKey", UncoveredConversionException(keyValueGeneric, nameof(CreateMethod))), - KeyValueGeneric keyValueGeneric when CreateSignature(keyValueGeneric.TypeSymbol, options) is var signature => keyValueGeneric.Type switch + KeyValueGeneric keyValueGeneric when CreateSignature(keyValueGeneric, options) is var signature => keyValueGeneric.Type switch { KeyValueGeneric.SupportedType.Dictionary => signature - .CreateScope($"return {Value} is {{ M: {{ }} x }} ? {AttributeValueUtilityFactory.ToDictionary}(x, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(keyValueGeneric.TValue, "a", "d", options, "o")}) : {Else(keyValueGeneric.TypeSymbol)};") + .CreateScope( + $"if ({Value} is null || {Value}.M is null)" + .CreateScope(keyValueGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.ToDictionary}({Value}.M, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(keyValueGeneric.TValue, "a", "d", options, "o")});") + ) .ToConversion(keyValueGeneric.TValue), KeyValueGeneric.SupportedType.LookUp => signature - .CreateScope($"return {Value} is {{ M: {{ }} x }} ? {AttributeValueUtilityFactory.ToLookup}(x, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(keyValueGeneric.TValue, "a", "d", options, "o")}) : {Else(keyValueGeneric.TypeSymbol)};") + .CreateScope( + $"if ({Value} is null || {Value}.M is null)" + .CreateScope(keyValueGeneric.ReturnNullOrThrow(DataMember)) + .Append($"return {AttributeValueUtilityFactory.ToLookup}({Value}.M, {MarshallerOptions.ParamReference}, {DataMember}, static (a, o, d) => {InvokeUnmarshallMethod(keyValueGeneric.TValue, "a", "d", options, "o")});") + ) .ToConversion(keyValueGeneric.TValue), _ => throw UncoveredConversionException(keyValueGeneric, nameof(CreateMethod)) }, - UnknownType => CreateCode(type, fn, options), - var typeIdentifier => throw UncoveredConversionException(typeIdentifier, nameof(CreateMethod)) + UnknownType => CreateCode(typeIdentifier, fn, options), + _ => throw UncoveredConversionException(typeIdentifier, nameof(CreateMethod)) }; } - private static string CreateSignature(ITypeSymbol typeSymbol, MarshallerOptions options) + private static string CreateSignature(TypeIdentifier typeIdentifier, MarshallerOptions options) { - return $"public static {typeSymbol.Representation().annotated} {GetDeserializationMethodName(typeSymbol)}(AttributeValue? {Value}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)"; + return $"public static {typeIdentifier.AnnotatedString} {GetDeserializationMethodName(typeIdentifier.TypeSymbol)}(AttributeValue? {Value}, {options.FullName} {MarshallerOptions.ParamReference}, string? {DataMember} = null)"; } + private static IEnumerable CreateTypeContents(IEnumerable arguments, Func getDynamoDbProperties, MarshallerOptions options) { - var hashSet = new HashSet(SymbolEqualityComparer.IncludeNullability); + var hashSet = new HashSet(TypeIdentifier.Nullable); return arguments.SelectMany(x => CodeFactory.Create( x.EntityTypeSymbol, @@ -147,19 +222,14 @@ private static IEnumerable CreateTypeContents(IEnumerable ObjectAssignmentBlock(bool useParentheses, IEnumerable assignments, bool applySemiColon) @@ -192,11 +262,12 @@ private static IEnumerable ObjectAssignmentBlock(bool useParentheses, IE } - internal static IEnumerable RootSignature(ITypeSymbol typeSymbol, string rootTypeName) + + internal static IEnumerable RootSignature(TypeIdentifier typeIdentifier) { - return $"public {rootTypeName} {Marshaller.UnmarshalMethodName}(Dictionary<{nameof(String)}, {Constants.AWSSDK_DynamoDBv2.AttributeValue}> entity)".CreateScope( + return $"public {typeIdentifier.AnnotatedString} {Marshaller.UnmarshalMethodName}(Dictionary<{nameof(String)}, {Constants.AWSSDK_DynamoDBv2.AttributeValue}> entity)".CreateScope( "ArgumentNullException.ThrowIfNull(entity);", - $"return {UnMarshallerClass}.{GetDeserializationMethodName(typeSymbol)}(entity, {MarshallerOptions.FieldReference});"); + $"return {UnMarshallerClass}.{GetDeserializationMethodName(typeIdentifier.TypeSymbol)}(entity, {MarshallerOptions.FieldReference});"); } private static IEnumerable<(string DataMember, string ParameterName)> TryGetMatchedConstructorArguments(ITypeSymbol typeSymbol) { diff --git a/src/DynamoDBGenerator.SourceGenerator/MarshallerFactory.cs b/src/DynamoDBGenerator.SourceGenerator/MarshallerFactory.cs index 8e14e071..d9b22a0a 100644 --- a/src/DynamoDBGenerator.SourceGenerator/MarshallerFactory.cs +++ b/src/DynamoDBGenerator.SourceGenerator/MarshallerFactory.cs @@ -17,22 +17,22 @@ private static IEnumerable CreateImplementations( { foreach (var argument in arguments) { - var (expressionValueMethod, valueTrackerTypeName) = AttributeExpressionValue.RootSignature(parentType, argument.ArgumentType); - var (expressionMethodName, nameTrackerTypeName) = AttributeExpressionName.RootSignature(parentType, argument.EntityTypeSymbol); + var (expressionValueMethod, valueTrackerTypeName) = AttributeExpressionValue.RootSignature(parentType, argument.ArgumentType.TypeSymbol); + var (expressionMethodName, nameTrackerTypeName) = AttributeExpressionName.RootSignature(parentType, argument.EntityTypeSymbol.TypeSymbol); var constructor = $"public {argument.ImplementationName}({options.FullName} {MarshallerOptions.ParamReference})" .CreateScope($"{MarshallerOptions.FieldReference} = {MarshallerOptions.ParamReference};", - $"{KeyMarshaller.PrimaryKeyMarshallerReference} = {KeyMarshaller.AssignmentRoot(argument.EntityTypeSymbol)};"); + $"{KeyMarshaller.PrimaryKeyMarshallerReference} = {KeyMarshaller.AssignmentRoot(argument.EntityTypeSymbol.TypeSymbol)};"); var implementation = constructor - .Concat(RootSignature(argument.EntityTypeSymbol, argument.AnnotatedEntityType)) - .Concat(UnMarshaller.RootSignature(argument.EntityTypeSymbol, argument.AnnotatedEntityType)) - .Concat(KeyMarshaller.IndexKeyMarshallerRootSignature(argument.EntityTypeSymbol)) + .Concat(RootSignature(argument.EntityTypeSymbol)) + .Concat(UnMarshaller.RootSignature(argument.EntityTypeSymbol)) + .Concat(KeyMarshaller.IndexKeyMarshallerRootSignature(argument.EntityTypeSymbol.TypeSymbol)) .Concat(expressionValueMethod) .Append(expressionMethodName) .Append(KeyMarshaller.PrimaryKeyMarshallerDeclaration) .Prepend(options.FieldDeclaration) - .ScopeTo($"file sealed class {argument.ImplementationName}: {Interface}<{argument.AnnotatedEntityType}, {argument.AnnotatedArgumentType}, {nameTrackerTypeName}, {valueTrackerTypeName}>"); + .ScopeTo($"file sealed class {argument.ImplementationName}: {Interface}<{argument.EntityTypeSymbol.AnnotatedString}, {argument.ArgumentType.AnnotatedString}, {nameTrackerTypeName}, {valueTrackerTypeName}>"); foreach (var row in implementation) yield return row; @@ -44,12 +44,12 @@ private static IEnumerable PublicProperties(ITypeSymbol parentType, Dyna { foreach (var argument in arguments) { - var valueTrackerTypeName = AttributeExpressionValue.GloballyAccessibleName(parentType, argument.ArgumentType); - var nameTrackerTypeName = AttributeExpressionName.GloballyAccessibleName(parentType, argument.EntityTypeSymbol); + var valueTrackerTypeName = AttributeExpressionValue.GloballyAccessibleName(parentType, argument.ArgumentType.TypeSymbol); + var nameTrackerTypeName = AttributeExpressionName.GloballyAccessibleName(parentType, argument.EntityTypeSymbol.TypeSymbol); yield return options.TryInstantiate() switch { - { } arg => $"public static {Interface}<{argument.AnnotatedEntityType}, {argument.AnnotatedArgumentType}, {nameTrackerTypeName}, {valueTrackerTypeName}> {argument.AccessName} {{ get; }} = new {argument.ImplementationName}({arg});", - null => $"public static {Interface}<{argument.AnnotatedEntityType}, {argument.AnnotatedArgumentType}, {nameTrackerTypeName}, {valueTrackerTypeName}> {argument.AccessName}({options.FullName} options) => new {argument.ImplementationName}(options);" + { } arg => $"public static {Interface}<{argument.EntityTypeSymbol.AnnotatedString}, {argument.ArgumentType.AnnotatedString}, {nameTrackerTypeName}, {valueTrackerTypeName}> {argument.AccessName} {{ get; }} = new {argument.ImplementationName}({arg});", + null => $"public static {Interface}<{argument.EntityTypeSymbol.AnnotatedString}, {argument.ArgumentType.AnnotatedString}, {nameTrackerTypeName}, {valueTrackerTypeName}> {argument.AccessName}({options.FullName} options) => new {argument.ImplementationName}(options);" }; } } diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/CodeFactory.cs b/src/DynamoDBGenerator.SourceGenerator/Types/CodeFactory.cs index 0266b834..ea42919a 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/CodeFactory.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/CodeFactory.cs @@ -1,10 +1,11 @@ +using DynamoDBGenerator.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; namespace DynamoDBGenerator.SourceGenerator.Types; public readonly struct CodeFactory { - public CodeFactory(IEnumerable lines, IEnumerable dependantTypes) + public CodeFactory(IEnumerable lines, IEnumerable dependantTypes) { _lines = lines; _dependantTypes = dependantTypes; @@ -13,7 +14,7 @@ public CodeFactory(IEnumerable lines, IEnumerable dependant public CodeFactory(IEnumerable lines) { _lines = lines; - _dependantTypes = Enumerable.Empty(); + _dependantTypes = Enumerable.Empty(); } /// @@ -24,12 +25,12 @@ public CodeFactory(IEnumerable lines) /// /// The types that are are dependant on. /// - private readonly IEnumerable _dependantTypes; + private readonly IEnumerable _dependantTypes; public static IEnumerable Create( - ITypeSymbol typeSymbol, - Func codeSelector, - ISet handledTypes + TypeIdentifier typeSymbol, + Func codeSelector, + ISet handledTypes ) { // We already support the type. @@ -45,4 +46,5 @@ ISet handledTypes foreach (var nestedCode in Create(nestedTypeSymbol, codeSelector, handledTypes)) yield return nestedCode; } + } \ No newline at end of file diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs b/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs index 28785234..316cc8f2 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs @@ -11,17 +11,16 @@ public readonly struct DataMember private DataMember(in ISymbol symbol, in string fieldName, in ITypeSymbol type, in bool isAssignable) { Name = fieldName; - Type = type; BaseSymbol = symbol; IsAssignable = isAssignable; - NameAsPrivateField = fieldName.ToPrivateFieldFromPascal(); NameAsCamelCase = fieldName.ToCamelCaseFromPascal(); + TypeIdentifier = type.TypeIdentifier(); } public static DataMember FromField(in IFieldSymbol fieldSymbol) { - var symbol = (ISymbol)fieldSymbol; + ISymbol symbol = fieldSymbol; var name = fieldSymbol.Name; var type = fieldSymbol.Type; @@ -30,7 +29,7 @@ public static DataMember FromField(in IFieldSymbol fieldSymbol) public static DataMember FromProperty(in IPropertySymbol property) { - var symbol = (ISymbol)property; + ISymbol symbol = property; var name = property.Name; var type = property.Type; @@ -47,13 +46,8 @@ public static DataMember FromProperty(in IPropertySymbol property) /// public string Name { get; } - + public TypeIdentifier TypeIdentifier { get; } public string NameAsPrivateField { get; } public string NameAsCamelCase { get; } - - /// - /// The type of the data member. - /// - public ITypeSymbol Type { get; } public bool IsAssignable { get; } } diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDBMarshallerArguments.cs b/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDBMarshallerArguments.cs index 4dbcf072..30b413e9 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDBMarshallerArguments.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDBMarshallerArguments.cs @@ -1,27 +1,26 @@ using DynamoDBGenerator.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; + namespace DynamoDBGenerator.SourceGenerator.Types; public readonly record struct DynamoDBMarshallerArguments { - public DynamoDBMarshallerArguments(INamedTypeSymbol entityTypeSymbol, INamedTypeSymbol? argumentType, string? propertyName) + public DynamoDBMarshallerArguments( + INamedTypeSymbol entityTypeSymbol, + INamedTypeSymbol? argumentType, + string? propertyName + ) { - EntityTypeSymbol = (INamedTypeSymbol)entityTypeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated); + EntityTypeSymbol = entityTypeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated).TypeIdentifier(); ArgumentType = argumentType is null ? EntityTypeSymbol - : (INamedTypeSymbol)argumentType.WithNullableAnnotation(NullableAnnotation.NotAnnotated); - AccessName = propertyName ?? $"{EntityTypeSymbol.Name}Marshaller"; + : argumentType.WithNullableAnnotation(NullableAnnotation.NotAnnotated).TypeIdentifier(); + AccessName = propertyName ?? $"{EntityTypeSymbol.TypeSymbol.Name}Marshaller"; ImplementationName = $"{AccessName}Implementation"; - - AnnotatedEntityType = EntityTypeSymbol.Representation().annotated; - AnnotatedArgumentType = argumentType is null ? AnnotatedEntityType : ArgumentType.Representation().annotated; - } + public string ImplementationName { get; } - public INamedTypeSymbol EntityTypeSymbol { get; } - public INamedTypeSymbol ArgumentType { get; } + public TypeIdentifier EntityTypeSymbol { get; } + public TypeIdentifier ArgumentType { get; } public string AccessName { get; } - public string AnnotatedEntityType { get; } - public string AnnotatedArgumentType { get; } - -} +} \ No newline at end of file diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDbDataMember.cs b/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDbDataMember.cs index c4b27f5e..e10480f0 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDbDataMember.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/DynamoDbDataMember.cs @@ -96,7 +96,7 @@ public static bool IsIgnored(AttributeData[] attributes) when GetStringArrayFromConstructor(x.Attribute).FirstOrDefault() is { } index => index, Constants.AWSSDK_DynamoDBv2.Attribute.DynamoDBGlobalSecondaryIndexRangeKey when GetStringArrayFromConstructor(x.Attribute).FirstOrDefault() is { } index => index, - _ => throw new NotSupportedException(x.DataMember.DataMember.Type.ToDisplayString()) + _ => throw new NotSupportedException(x.DataMember.DataMember.TypeIdentifier.TypeSymbol.ToDisplayString()) }) .Select(x => { diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/MarshallerOptions.cs b/src/DynamoDBGenerator.SourceGenerator/Types/MarshallerOptions.cs index 3ff07b36..56fa0de7 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/MarshallerOptions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/MarshallerOptions.cs @@ -17,7 +17,9 @@ public readonly struct MarshallerOptions public string FullName { get; } public string FieldDeclaration { get; } - + public bool IsUnknown(TypeIdentifier typeIdentifier) => typeIdentifier is UnknownType && IsConvertable(typeIdentifier) is false; + public bool IsConvertable(TypeIdentifier typeIdentifier) => typeIdentifier.TypeSymbol.TypeKind is TypeKind.Enum || Converters.ContainsKey(typeIdentifier.TypeSymbol); + private MarshallerOptions( INamedTypeSymbol originalType, INamedTypeSymbol convertersType, @@ -64,23 +66,22 @@ int enumStrategy return null; } - public string? TryReadConversion(ITypeSymbol typeSymbol, string attributeValueParam) + public string? TryReadConversion(TypeIdentifier typeIdentifier, string attributeValueParam) { // Converters comes first so that you your customized converters are always prioritized. - if (Converters.TryGetValue(typeSymbol, out var match)) + if (Converters.TryGetValue(typeIdentifier.TypeSymbol, out var match)) return $"{ParamReference}.{ConvertersProperty}.{match.Key}.Read({attributeValueParam})"; - if (typeSymbol.TypeKind is TypeKind.Enum) + if (typeIdentifier.TypeSymbol.TypeKind is TypeKind.Enum) { - var original = typeSymbol.Representation().original; return _enumStrategy switch { - ConversionStrategy.Integer => $"Int32.TryParse({attributeValueParam}.N, out var @enum) ? ({original}?)@enum : null", - ConversionStrategy.Name => $"Enum.TryParse<{original}>({attributeValueParam}.S, false, out var @enum) ? ({original}?)@enum : null", + ConversionStrategy.Integer => $"(Int32.TryParse({attributeValueParam}.N, out var e) ? ({typeIdentifier.UnannotatedString}?) e : null)", + ConversionStrategy.Name => $"(Enum.TryParse<{typeIdentifier.UnannotatedString}>({attributeValueParam}.S, false, out var e) ? ({typeIdentifier.UnannotatedString}?) e : null)", ConversionStrategy.NameCI or ConversionStrategy.LowerCase or ConversionStrategy.UpperCase - => $"Enum.TryParse<{original}>({attributeValueParam}.S, true, out var @enum) ? ({original}?)@enum : null", + => $"(Enum.TryParse<{typeIdentifier.UnannotatedString}>({attributeValueParam}.S, true, out var e) ? ({typeIdentifier.UnannotatedString}?) e : null)", _ => throw new ArgumentException($"Could not resolve enum conversion strategy from value '{_enumStrategy}'.") }; } @@ -88,10 +89,6 @@ or ConversionStrategy.UpperCase return null; } - public bool IsConvertable(ITypeSymbol typeSymbol) - { - return typeSymbol.TypeKind is TypeKind.Enum || Converters.ContainsKey(typeSymbol); - } private Dictionary> Converters { get; } diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/TypeIdentifier.cs b/src/DynamoDBGenerator.SourceGenerator/Types/TypeIdentifier.cs index 2d5004df..afce9fb5 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/TypeIdentifier.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/TypeIdentifier.cs @@ -1,49 +1,201 @@ +using System.Collections.Concurrent; using DynamoDBGenerator.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; + namespace DynamoDBGenerator.SourceGenerator.Types; -public abstract record TypeIdentifier(ITypeSymbol TypeSymbol) +public abstract record TypeIdentifier { - public ITypeSymbol TypeSymbol { get; } = TypeSymbol; + private static readonly ConcurrentDictionary RepresentationDictionary = + new(SymbolEqualityComparer.IncludeNullability); + + internal static readonly IEqualityComparer Default = + new TypeSymbolDelegator(SymbolEqualityComparer.Default); + + internal static readonly IEqualityComparer Nullable = + new TypeSymbolDelegator(SymbolEqualityComparer.IncludeNullability); + + protected TypeIdentifier(ITypeSymbol typeSymbol) + { + IsNullable = typeSymbol switch + { + { + IsReferenceType: true, NullableAnnotation: NullableAnnotation.None or NullableAnnotation.Annotated + } => true, + { IsReferenceType: true, NullableAnnotation: NullableAnnotation.NotAnnotated } => false, + { IsValueType: true, OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } => true, + { IsValueType: true } => false, + _ => throw new ArgumentOutOfRangeException( + $"Could not determine nullablity of type '{typeSymbol.ToDisplayString()}'.") + }; + + TypeSymbol = typeSymbol; + var (annotated, original) = Representation(typeSymbol); + UnannotatedString = original; + AnnotatedString = annotated; + IsNumeric = IsNumericMethod(typeSymbol); + } + + public bool IsNullable { get; } + public bool IsNumeric { get; } + public string AnnotatedString { get; } + public string UnannotatedString { get; } + + public ITypeSymbol TypeSymbol { get; } + + private static (string annotated, string original) Representation(ITypeSymbol typeSymbol) + { + return RepresentationDictionary.GetOrAdd(typeSymbol, x => + { + var displayString = x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return RepresentationDictionary[typeSymbol] = (ToString(typeSymbol, displayString), displayString); + }); + + static string ToString(ITypeSymbol x, string displayString) + { + if (x is IArrayTypeSymbol arrayTypeSymbol) + { + var result = ToString(arrayTypeSymbol.ElementType, + arrayTypeSymbol.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + + return x.NullableAnnotation switch + { + NullableAnnotation.Annotated or NullableAnnotation.None => $"{result}[]?", + NullableAnnotation.NotAnnotated => $"{result}[]", + _ => throw new ArgumentException(ExceptionMessage(x)) + }; + } + + if (x is not INamedTypeSymbol namedTypeSymbol || namedTypeSymbol.TypeArguments.Length is 0) + return x.NullableAnnotation switch + { + // Having `Annotated` and `None` produce append '?' is fine as long as `SuffixedTypeSymbolNameFactory` is giving them different names. Otherwise we could create broken signatures due to duplication. + NullableAnnotation.Annotated or NullableAnnotation.None => $"{displayString}?", + NullableAnnotation.NotAnnotated => displayString, + _ => throw new ArgumentException(ExceptionMessage(x)) + }; + + if (namedTypeSymbol.OriginalDefinition.SpecialType is SpecialType.System_Nullable_T) + return displayString; + + if (namedTypeSymbol.IsTupleType) + { + var tupleElements = namedTypeSymbol.TupleElements + .Select(y => + $"{ToString(y.Type, $"{y.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}")} {y.Name}"); + return $"({string.Join(", ", tupleElements)})"; + } + + var index = displayString.AsSpan().IndexOf('<'); + if (index == -1) + return displayString; + + var typeWithoutGenericParameters = displayString.Substring(0, index); + var typeParameters = string.Join(", ", + namedTypeSymbol.TypeArguments.Select(y => + ToString(y, y.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))); + return namedTypeSymbol.NullableAnnotation switch + { + // Having `Annotated` and `None` produce append '?' is fine as long as `SuffixedTypeSymbolNameFactory` is giving them different names. Otherwise we could create broken signatures due to duplication. + NullableAnnotation.Annotated or NullableAnnotation.None => + $"{typeWithoutGenericParameters}<{typeParameters}>?", + NullableAnnotation.NotAnnotated => $"{typeWithoutGenericParameters}<{typeParameters}>", + _ => throw new ArgumentException(ExceptionMessage(namedTypeSymbol)) + }; + + static string ExceptionMessage(ISymbol typeSymbol) + { + return $"Could nullable annotation on type: {typeSymbol.ToDisplayString()}"; + } + } + } + + private static bool IsNumericMethod(ITypeSymbol typeSymbol) + { + return typeSymbol.SpecialType + is SpecialType.System_Int16 + or SpecialType.System_Byte + or SpecialType.System_Int32 + or SpecialType.System_Int64 + or SpecialType.System_SByte + or SpecialType.System_UInt16 + or SpecialType.System_UInt32 + or SpecialType.System_UInt64 + or SpecialType.System_Decimal + or SpecialType.System_Double + or SpecialType.System_Single; + } + + public string ReturnNullOrThrow(string dataMember) + { + return IsNullable + ? "return null;" + : $"throw {Constants.DynamoDBGenerator.ExceptionHelper.NullExceptionMethod}({dataMember});"; + } + + private class TypeSymbolDelegator : IEqualityComparer + { + private readonly IEqualityComparer _comparer; + + public TypeSymbolDelegator(IEqualityComparer comparer) + { + _comparer = comparer; + } + + public bool Equals(TypeIdentifier? x, TypeIdentifier? y) + { + return ReferenceEquals(x, y) || x switch + { + null => false, + _ => y is not null && _comparer.Equals(x.TypeSymbol, y.TypeSymbol) + }; + } + + public int GetHashCode(TypeIdentifier obj) + { + return _comparer.GetHashCode(obj.TypeSymbol); + } + } } public sealed record UnknownType(ITypeSymbol TypeSymbol) : TypeIdentifier(TypeSymbol); public sealed record KeyValueGeneric : TypeIdentifier { - public enum SupportedType { LookUp = 1, Dictionary = 2 } - private KeyValueGeneric(in ITypeSymbol typeSymbol, in ITypeSymbol tKey, in ITypeSymbol tValue, in SupportedType supportedType) : base(typeSymbol) + private KeyValueGeneric(in ITypeSymbol typeSymbol, in ITypeSymbol tKey, in ITypeSymbol tValue, + in SupportedType supportedType) : base(typeSymbol) { Type = supportedType; TKey = tKey; - TValue = tValue; + TValue = tValue.TypeIdentifier(); } + public SupportedType Type { get; } // ReSharper disable once InconsistentNaming public ITypeSymbol TKey { get; } // ReSharper disable once InconsistentNaming - public ITypeSymbol TValue { get; } + public TypeIdentifier TValue { get; } public static KeyValueGeneric? CreateInstance(in ITypeSymbol typeSymbol) { if (typeSymbol is not INamedTypeSymbol type) return null; - if (type is not {IsGenericType: true, TypeArguments.Length: 2}) + if (type is not { IsGenericType: true, TypeArguments.Length: 2 }) return null; SupportedType? supported = type switch { - {Name: "ILookup"} => SupportedType.LookUp, - {Name: "Dictionary" or "IReadOnlyDictionary" or "IDictionary"} => SupportedType.Dictionary, + { Name: "ILookup" } => SupportedType.LookUp, + { Name: "Dictionary" or "IReadOnlyDictionary" or "IDictionary" } => SupportedType.Dictionary, _ => null }; return supported is null @@ -54,7 +206,6 @@ private KeyValueGeneric(in ITypeSymbol typeSymbol, in ITypeSymbol tKey, in IType public sealed record SingleGeneric : TypeIdentifier { - public enum SupportedType { Nullable = 1, @@ -70,11 +221,11 @@ private SingleGeneric(ITypeSymbol type, ITypeSymbol innerType, in SupportedType { Type = supportedType; - T = innerType; + T = innerType.TypeIdentifier(); } - public SupportedType Type { get; } - public ITypeSymbol T { get; } + public SupportedType Type { get; } + public TypeIdentifier T { get; } public static SingleGeneric? CreateInstance(in ITypeSymbol typeSymbol) { @@ -84,25 +235,33 @@ private SingleGeneric(ITypeSymbol type, ITypeSymbol innerType, in SupportedType if (typeSymbol is not INamedTypeSymbol type) return null; - if (type is not {IsGenericType: true, TypeArguments.Length: 1}) + if (type is not { IsGenericType: true, TypeArguments.Length: 1 }) return null; SupportedType? supported = type switch { _ when type.TryGetNullableValueType() is not null => SupportedType.Nullable, - _ when type.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.List" => SupportedType.List, - {Name: "ISet" } => SupportedType.Set, - _ when type.AllInterfaces.Any(x => x is {Name: "ISet"}) => SupportedType.Set, - {Name: "IReadOnlySet" } => SupportedType.Set, - _ when type.AllInterfaces.Any(x => x is {Name: "IReadOnlySet"}) => SupportedType.Set, - {OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_ICollection_T} => SupportedType.ICollection, - _ when type.AllInterfaces.Any(x => x is {OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_ICollection_T}) => SupportedType.ICollection, - {OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_IReadOnlyCollection_T} => SupportedType.IReadOnlyCollection, - _ when type.AllInterfaces.Any(x => x is {OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_IReadOnlyCollection_T}) => SupportedType.IReadOnlyCollection, - {OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_IEnumerable_T} => SupportedType.IEnumerable, + _ when type.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.List" => SupportedType + .List, + { Name: "ISet" } => SupportedType.Set, + _ when type.AllInterfaces.Any(x => x is { Name: "ISet" }) => SupportedType.Set, + { Name: "IReadOnlySet" } => SupportedType.Set, + _ when type.AllInterfaces.Any(x => x is { Name: "IReadOnlySet" }) => SupportedType.Set, + { OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_ICollection_T } => SupportedType + .ICollection, + _ when type.AllInterfaces.Any(x => x is + { OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_ICollection_T }) => + SupportedType.ICollection, + { OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_IReadOnlyCollection_T } => + SupportedType.IReadOnlyCollection, + _ when type.AllInterfaces.Any(x => x is + { OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_IReadOnlyCollection_T }) => + SupportedType.IReadOnlyCollection, + { OriginalDefinition.SpecialType: SpecialType.System_Collections_Generic_IEnumerable_T } => SupportedType + .IEnumerable, _ => null }; return supported is null ? null : new SingleGeneric(type, type.TypeArguments[0], supported.Value); } -} +} \ No newline at end of file diff --git a/src/DynamoDBGenerator/Internal/MarshallHelper.cs b/src/DynamoDBGenerator/Internal/MarshallHelper.cs index 030a43d8..b40c0cf0 100644 --- a/src/DynamoDBGenerator/Internal/MarshallHelper.cs +++ b/src/DynamoDBGenerator/Internal/MarshallHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Amazon.DynamoDBv2.Model; using static System.Runtime.InteropServices.CollectionsMarshal; @@ -17,6 +18,12 @@ public static class MarshallHelper #pragma warning disable CS1591 public static AttributeValue Null { get; } = new() { NULL = true }; + [return: NotNullIfNotNull(nameof(dict))] + public static AttributeValue? ToAttributeValue( + [NotNullIfNotNull(nameof(dict))] Dictionary? dict + ) => dict is null + ? null + : new AttributeValue { M = dict }; public static AttributeValue FromDictionary( IEnumerable> dictionary, diff --git a/tests/DynamoDBGenerator.SourceGenerator.Benchmarks/Models/Address.cs b/tests/DynamoDBGenerator.SourceGenerator.Benchmarks/Models/Address.cs index 97d8c91a..996e1bd2 100644 --- a/tests/DynamoDBGenerator.SourceGenerator.Benchmarks/Models/Address.cs +++ b/tests/DynamoDBGenerator.SourceGenerator.Benchmarks/Models/Address.cs @@ -4,7 +4,7 @@ public class Address { public string Id { get; set; } = null!; - public string Street { get; set; } = null!; + public string? Street { get; set; } = null!; public PostalCode PostalCode { get; set; } = null!; diff --git a/tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBGenerator.SourceGenerator.Tests.csproj b/tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBGenerator.SourceGenerator.Tests.csproj index 6e76d5d7..7bf1d8cd 100644 --- a/tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBGenerator.SourceGenerator.Tests.csproj +++ b/tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBGenerator.SourceGenerator.Tests.csproj @@ -10,14 +10,10 @@ - + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + +