diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs index 62d8559f..7efb48ca 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/NotNullEvaluationExtensions.cs @@ -31,18 +31,47 @@ private static string CreateException(in string accessPattern) return $"throw {Constants.DynamoDBGenerator.ExceptionHelper.NullExceptionMethod}(nameof({accessPattern}));"; } - public static string NotNullIfStatement(this ITypeSymbol typeSymbol, in string accessPattern, in 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) + { + return NotNullIfStatement(typeSymbol, accessPattern, obj: truthy); + } + + private static IEnumerable NotNullIfStatement(this ITypeSymbol typeSymbol, string accessPattern, object obj) { if (Expression(typeSymbol, accessPattern) is not { } expression) - return truthy; - - var ifClause = $"if ({expression}) {{ {truthy} }}"; - return typeSymbol.NullableAnnotation switch { - NullableAnnotation.None or NullableAnnotation.Annotated => ifClause, - NullableAnnotation.NotAnnotated => $"{ifClause} else {{ {CreateException(in accessPattern)} }}", - _ => throw new ArgumentOutOfRangeException(typeSymbol.ToDisplayString()) - }; + 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 + { + 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))), + _ => throw new ArgumentOutOfRangeException(typeSymbol.ToDisplayString()) + }; + + foreach (var element in enumerable) + yield return element; + } + } @@ -72,4 +101,4 @@ public static string NotNullIfStatement(this ITypeSymbol typeSymbol, in string a } -} \ No newline at end of file +} diff --git a/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs b/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs index 2871bed8..8685a1f9 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Extensions/StringExtensions.cs @@ -2,37 +2,56 @@ namespace DynamoDBGenerator.SourceGenerator.Extensions; public static class StringExtensions { + public static string ToCamelCaseFromPascal(this string str, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) + { + return ToCamelCaseFromPascal(str.AsSpan(), memberName).ToString(); + } - private static readonly IDictionary IndentCache = new Dictionary + public static string ToPrivateFieldFromPascal(this string str, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) { - {0, ""}, - {1, " "}, - {2, " "}, - {3, " "}, - {4, " "} - }; - - private static string Indent(int level) + return ToPrivateFieldFromPascal(str.AsSpan(), memberName).ToString(); + } + + public static ReadOnlySpan ToPrivateFieldFromPascal(this ReadOnlySpan span, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) { - if (IndentCache.TryGetValue(level, out var indent)) return indent; + if (span.Length is 0) + throw new ArgumentException($"Null or Empty string was provided from '{memberName}'"); - indent = new string(' ', level * 4); - IndentCache[level] = indent; + var array = new char[span.Length + 1]; - return indent; + array[0] = '_'; + array[1] = Char.ToLowerInvariant(span[0]); + + // Skip first element since we handled it manually. + for (var i = 1; i < span.Length; i++) + array[i + 1] = span[i]; + + return array; } - public static IEnumerable CreateScope(this string header, IEnumerable content, int indentLevel) + public static ReadOnlySpan ToCamelCaseFromPascal(this ReadOnlySpan span, [System.Runtime.CompilerServices.CallerMemberName] string? memberName = null) { - var indent = Indent(indentLevel); + if (span.Length is 0) + throw new ArgumentException($"Null or Empty string was provided from '{memberName}'"); - yield return $"{indent}{header}"; - yield return string.Intern($"{indent}{{"); + if (char.IsLower(span[0])) + return span; - foreach (var s in content) - yield return $"{Indent(indentLevel + 1)}{s}"; + var array = new char[span.Length]; - yield return string.Intern($"{indent}}}"); + array[0] = Char.ToLowerInvariant(span[0]); + + // Skip first element since we handled it manually. + for (var i = 1; i < span.Length; i++) + array[i] = span[i]; + + return array; } + + public static IEnumerable ScopeTo(this IEnumerable content, string header) + { + return CreateScope(header, content); + } + public static IEnumerable CreateScope(this string header, IEnumerable content) { yield return header; @@ -53,7 +72,7 @@ public static IEnumerable CreateScope(this string header, string content yield return "}"; } - + public static IEnumerable CreateScope(this string header, string content, string second) { yield return header; @@ -81,4 +100,4 @@ public static string ToAlphaNumericMethodName(this string txt) return new string(arr, 0, index); } -} \ No newline at end of file +} diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs index 013de6e1..c9f30ae1 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs @@ -24,17 +24,17 @@ internal static IEnumerable CreateClasses(IEnumerable TypeContent( ITypeSymbol typeSymbol, - (bool IsUnknown, DynamoDbDataMember DDB, string IfBranchAlias, string DbRef, string NameRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers, + (bool IsUnknown, DynamoDbDataMember DDB, string DbRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers, string structName) { - const string self = "_self"; + const string self = "_this"; var constructorFieldAssignments = dataMembers .Select(x => { var ternaryExpressionName = $"{ConstructorAttributeName} is null ? {@$"""#{x.DDB.AttributeName}"""}: {@$"$""{{{ConstructorAttributeName}}}.#{x.DDB.AttributeName}"""}"; return x.IsUnknown - ? $"{x.NameRef} = new (() => new {x.AttributeReference}({ternaryExpressionName}, {ConstructorSetName}));" - : $"{x.NameRef} = new (() => {ternaryExpressionName});"; + ? $"{x.DDB.DataMember.NameAsPrivateField} = new (() => new {x.AttributeReference}({ternaryExpressionName}, {ConstructorSetName}));" + : $"{x.DDB.DataMember.NameAsPrivateField} = new (() => {ternaryExpressionName});"; }) .Append($"{SetFieldName} = {ConstructorSetName};") .Append($@"{self} = new(() => {ConstructorAttributeName} ?? throw new NotImplementedException(""Root element AttributeExpressionName reference.""));"); @@ -46,13 +46,13 @@ private static IEnumerable TypeContent( { if (fieldDeclaration.IsUnknown) { - yield return $"private readonly Lazy<{fieldDeclaration.AttributeReference}> {fieldDeclaration.NameRef};"; - yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.NameRef}.Value;"; + yield return $"private readonly Lazy<{fieldDeclaration.AttributeReference}> {fieldDeclaration.DDB.DataMember.NameAsPrivateField};"; + yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.DDB.DataMember.NameAsPrivateField}.Value;"; } else { - yield return $"private readonly Lazy {fieldDeclaration.NameRef};"; - yield return $"public string {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.NameRef}.Value;"; + yield return $"private readonly Lazy {fieldDeclaration.DDB.DataMember.NameAsPrivateField};"; + yield return $"public string {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.DDB.DataMember.NameAsPrivateField}.Value;"; } } @@ -69,19 +69,20 @@ private static IEnumerable TypeContent( yield return $"public override string ToString() => {self}.Value;"; } - private static IEnumerable YieldSelector((bool IsUnknown, DynamoDbDataMember DDB, string IfBranchAlias, string DbRef, string NameRef, string AttributeReference, string AttributeInterfaceName) x) + private static IEnumerable YieldSelector((bool IsUnknown, DynamoDbDataMember DDB, string DbRef, string AttributeReference, string AttributeInterfaceName) x) { + var camelCase = x.DDB.DataMember.NameAsCamelCase; if (x.IsUnknown) { - var scope = $@"if (new KeyValuePair(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {x.IfBranchAlias} && {SetFieldName}.Add({x.IfBranchAlias}))" - .CreateScope($"yield return {x.IfBranchAlias};") + var scope = $@"if (new KeyValuePair(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {camelCase} && {SetFieldName}.Add({camelCase}))" + .CreateScope($"yield return {camelCase};") .Concat($"foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{AttributeExpressionNameTrackerInterfaceAccessedNames}())".CreateScope("yield return x;")); - return $"if ({x.NameRef}.IsValueCreated)".CreateScope(scope); + return $"if ({x.DDB.DataMember.NameAsPrivateField}.IsValueCreated)".CreateScope(scope); } else { - return $@"if ({x.NameRef}.IsValueCreated && new KeyValuePair(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {x.IfBranchAlias} && {SetFieldName}.Add({x.IfBranchAlias}))".CreateScope($"yield return {x.IfBranchAlias};"); + return $@"if ({x.DDB.DataMember.NameAsPrivateField}.IsValueCreated && new KeyValuePair(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {camelCase} && {SetFieldName}.Add({camelCase}))".CreateScope($"yield return {camelCase};"); } } @@ -91,9 +92,7 @@ private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func ( IsUnknown: !options.IsConvertable(x.DataMember.Type) && x.DataMember.Type.TypeIdentifier() is UnknownType, DDB: x, - IfBranchAlias: $"__{x.DataMember.Name}__", DbRef: $"#{x.AttributeName}", - NameRef: $"_{x.DataMember.Name}NameRef", AttributeReference: TypeName(x.DataMember.Type), AttributeInterfaceName: AttributeExpressionNameTrackerInterface )) diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs index 3f6c61f0..27ed7444 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionValue.cs @@ -12,17 +12,17 @@ public static class AttributeExpressionValue private const string ValueProvider = "valueIdProvider"; private static IEnumerable TypeContents( ITypeSymbol typeSymbol, - (bool IsUnknown, DynamoDbDataMember DDB, string ValueRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers, + (bool IsUnknown, DynamoDbDataMember DDB, string AttributeReference, string AttributeInterfaceName)[] dataMembers, string structName, string interfaceName, MarshallerOptions options ) { - const string self = "_self"; + const string self = "_this"; var constructorFieldAssignments = dataMembers .Select(x => x.IsUnknown - ? $"_{x.DDB.DataMember.Name} = new (() => new {x.AttributeReference}({ValueProvider}, {MarshallerOptions.ParamReference}));" - : $"{x.ValueRef} = new ({ValueProvider});") + ? $"{x.DDB.DataMember.NameAsPrivateField} = new (() => new {x.AttributeReference}({ValueProvider}, {MarshallerOptions.ParamReference}));" + : $"{x.DDB.DataMember.NameAsPrivateField} = new ({ValueProvider});") .Append($"{self} = new({ValueProvider});") .Append($"{MarshallerOptions.FieldReference} = {MarshallerOptions.ParamReference};"); foreach (var fieldAssignment in $"public {structName}(Func {ValueProvider}, {MarshallerOptions.Name} options)".CreateScope(constructorFieldAssignments)) @@ -33,49 +33,55 @@ MarshallerOptions options { if (fieldDeclaration.IsUnknown) { - yield return $"private readonly Lazy<{fieldDeclaration.AttributeReference}> _{fieldDeclaration.DDB.DataMember.Name};"; - yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => _{fieldDeclaration.DDB.DataMember.Name}.Value;"; + yield return $"private readonly Lazy<{fieldDeclaration.AttributeReference}> {fieldDeclaration.DDB.DataMember.NameAsPrivateField};"; + yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.DDB.DataMember.NameAsPrivateField}.Value;"; } else { - yield return $"private readonly Lazy {fieldDeclaration.ValueRef};"; - yield return $"public string {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.ValueRef}.Value;"; + yield return $"private readonly Lazy {fieldDeclaration.DDB.DataMember.NameAsPrivateField};"; + yield return $"public string {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.DDB.DataMember.NameAsPrivateField}.Value;"; } } yield return $"private readonly Lazy {self};"; const string param = "entity"; - var enumerable = Enumerable.Empty(); - if (typeSymbol.IsNullable()) - { - enumerable = $"if ({param} is null)".CreateScope($"yield return new ({self}.Value, {AttributeValueUtilityFactory.Null});", "yield break;"); - } - else if (typeSymbol.IsReferenceType) + + var yields = (typeSymbol switch { - enumerable = $"if ({param} is null)".CreateScope($"throw {ExceptionHelper.NullExceptionMethod}(\"{structName}\");"); - } + var x when x.IsNullable() => $"if ({param} is null)".CreateScope($"yield return new ({self}.Value, {AttributeValueUtilityFactory.Null});", "yield break;"), + var x when x.IsReferenceType => $"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});") + ) + .ScopeTo($"IEnumerable> {interfaceName}.{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({typeSymbol.Representation().annotated} entity)"); - var yields = enumerable.Concat( - dataMembers - .Select(x => - { - var accessPattern = $"entity.{x.DDB.DataMember.Name}"; - return x.IsUnknown - ? $"if (_{x.DDB.DataMember.Name}.IsValueCreated) {x.DDB.DataMember.Type.NotNullIfStatement(accessPattern, $"foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({accessPattern})) {{ yield return x; }}")}" - : $"if ({x.ValueRef}.IsValueCreated) {x.DDB.DataMember.Type.NotNullIfStatement(accessPattern, $"yield return new ({x.ValueRef}.Value, {Marshaller.InvokeMarshallerMethod(x.DDB.DataMember.Type, $"entity.{x.DDB.DataMember.Name}", $"\"{x.DDB.DataMember.Name}\"", options, MarshallerOptions.FieldReference)} ?? {AttributeValueUtilityFactory.Null});")}"; - } - ) - .Append($"if ({self}.IsValueCreated) yield return new ({self}.Value, {Marshaller.InvokeMarshallerMethod(typeSymbol, "entity", $"\"{structName}\"", options, MarshallerOptions.FieldReference)} ?? {AttributeValueUtilityFactory.Null});") - ); - - foreach (var yield in - $"IEnumerable> {interfaceName}.{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({typeSymbol.Representation().annotated} entity)" - .CreateScope(yields)) + + foreach (var yield in yields) yield return yield; yield return $"public override string ToString() => {self}.Value;"; } + + private static IEnumerable YieldSelector((bool IsUnknown, DynamoDbDataMember DDB, string AttributeReference, string AttributeInterfaceName) x, MarshallerOptions options) + { + var accessPattern = $"entity.{x.DDB.DataMember.Name}"; + + if (x.IsUnknown) + { + return x.DDB.DataMember.Type.NotNullIfStatement( + accessPattern, + $"foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerAccessedValues}({accessPattern}))".CreateScope("yield return x;") + ) + .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});")); + + } internal static IEnumerable CreateExpressionAttributeValue(IEnumerable arguments, Func> getDynamoDbProperties, MarshallerOptions options) { // Using _comparer can double classes when there's a None nullable property mixed with a nullable property @@ -89,16 +95,14 @@ private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func() : fn(typeSymbol) .Select(x => { return ( - IsUnknown: !options.IsConvertable(x.DataMember.Type) && - x.DataMember.Type.TypeIdentifier() is UnknownType, + IsUnknown: !options.IsConvertable(x.DataMember.Type) && x.DataMember.Type.TypeIdentifier() is UnknownType, DDB: x, - ValueRef: $"_{x.DataMember.Name}ValueRef", AttributeReference: TypeName(x.DataMember.Type), AttributeInterfaceName: $"{Constants.DynamoDBGenerator.Marshaller.AttributeExpressionValueTrackerInterface}<{x.DataMember.Type.Representation().annotated}>" @@ -123,4 +127,4 @@ internal static (IEnumerable method, string typeName) RootSignature(ITyp $"return new {typeName}(incrementer.GetNext, {MarshallerOptions.FieldReference});" ), typeName); } -} \ No newline at end of file +} diff --git a/src/DynamoDBGenerator.SourceGenerator/Generations/KeyMarshaller.cs b/src/DynamoDBGenerator.SourceGenerator/Generations/KeyMarshaller.cs index f81ed572..d13d3084 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Generations/KeyMarshaller.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Generations/KeyMarshaller.cs @@ -24,7 +24,7 @@ private static IEnumerable CreateAssignment(string validateReference, st .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}"");")); - return $"if({validateReference})".CreateScope(innerContent); + return $"if ({validateReference})".CreateScope(innerContent); } private static IEnumerable MethodBody(ITypeSymbol typeSymbol, Func> fn, MarshallerOptions options) @@ -120,4 +120,4 @@ private static CodeFactory StaticAttributeValueDictionaryKeys(ITypeSymbol typeSy return new CodeFactory(code); } -} \ No newline at end of file +} diff --git a/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs b/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs index 0ae9398a..28785234 100644 --- a/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs +++ b/src/DynamoDBGenerator.SourceGenerator/Types/DataMember.cs @@ -1,3 +1,4 @@ +using DynamoDBGenerator.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; namespace DynamoDBGenerator.SourceGenerator.Types; @@ -13,11 +14,14 @@ private DataMember(in ISymbol symbol, in string fieldName, in ITypeSymbol type, Type = type; BaseSymbol = symbol; IsAssignable = isAssignable; + + NameAsPrivateField = fieldName.ToPrivateFieldFromPascal(); + NameAsCamelCase = fieldName.ToCamelCaseFromPascal(); } public static DataMember FromField(in IFieldSymbol fieldSymbol) { - var symbol = (ISymbol) fieldSymbol; + var symbol = (ISymbol)fieldSymbol; var name = fieldSymbol.Name; var type = fieldSymbol.Type; @@ -26,7 +30,7 @@ public static DataMember FromField(in IFieldSymbol fieldSymbol) public static DataMember FromProperty(in IPropertySymbol property) { - var symbol = (ISymbol) property; + var symbol = (ISymbol)property; var name = property.Name; var type = property.Type; @@ -43,9 +47,13 @@ public static DataMember FromProperty(in IPropertySymbol property) /// public string Name { get; } + + public string NameAsPrivateField { get; } + public string NameAsCamelCase { get; } + /// /// The type of the data member. /// public ITypeSymbol Type { get; } public bool IsAssignable { get; } -} \ No newline at end of file +}