Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix NameTracker bug #79

Merged
merged 12 commits into from
Sep 29, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public static class AttributeExpressionName
{

private const string ConstructorAttributeName = "nameRef";
private const string ConstructorSetName = "set";
private const string SetFieldName = "___set___";

private static readonly Func<ITypeSymbol, string> TypeName = TypeExtensions.SuffixedTypeSymbolNameFactory("Names", SymbolEqualityComparer.Default);
internal static IEnumerable<string> CreateClasses(IEnumerable<DynamoDBMarshallerArguments> arguments, Func<ITypeSymbol, ImmutableArray<DynamoDbDataMember>> getDynamoDbProperties, MarshallerOptions options)
Expand All @@ -22,7 +24,7 @@ internal static IEnumerable<string> CreateClasses(IEnumerable<DynamoDBMarshaller
}
private static IEnumerable<string> TypeContent(
ITypeSymbol typeSymbol,
(bool IsUnknown, DynamoDbDataMember DDB, string NameRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers,
(bool IsUnknown, DynamoDbDataMember DDB, string IfBranchAlias, string DbRef, string NameRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers,
string structName)
{
const string self = "_self";
Expand All @@ -31,20 +33,21 @@ private static IEnumerable<string> TypeContent(
{
var ternaryExpressionName = $"{ConstructorAttributeName} is null ? {@$"""#{x.DDB.AttributeName}"""}: {@$"$""{{{ConstructorAttributeName}}}.#{x.DDB.AttributeName}"""}";
return x.IsUnknown
? $"_{x.DDB.DataMember.Name} = new (() => new {x.AttributeReference}({ternaryExpressionName}));"
? $"{x.NameRef} = new (() => new {x.AttributeReference}({ternaryExpressionName}, {ConstructorSetName}));"
: $"{x.NameRef} = new (() => {ternaryExpressionName});";
})
.Append($"{SetFieldName} = {ConstructorSetName};")
.Append($@"{self} = new(() => {ConstructorAttributeName} ?? throw new NotImplementedException(""Root element AttributeExpressionName reference.""));");

foreach (var fieldAssignment in $"public {structName}(string? {ConstructorAttributeName})".CreateScope(constructorFieldAssignments))
foreach (var fieldAssignment in $"public {structName}(string? {ConstructorAttributeName}, HashSet<KeyValuePair<string, string>> {ConstructorSetName})".CreateScope(constructorFieldAssignments))
yield return fieldAssignment;

foreach (var fieldDeclaration in dataMembers)
{
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.NameRef};";
yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.NameRef}.Value;";
}
else
{
Expand All @@ -54,25 +57,42 @@ private static IEnumerable<string> TypeContent(
}
}
yield return $"private readonly Lazy<string> {self};";
yield return $"private readonly HashSet<KeyValuePair<string, string>> {SetFieldName};";

var yields = dataMembers
.Select(static x => x.IsUnknown
? $"if (_{x.DDB.DataMember.Name}.IsValueCreated) foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{AttributeExpressionNameTrackerInterfaceAccessedNames}()) {{ yield return x; }}"
: $@"if ({x.NameRef}.IsValueCreated) yield return new ({x.NameRef}.Value, ""{x.DDB.AttributeName}"");"
)
.SelectMany(YieldSelector)
.Append($@"if ({self}.IsValueCreated) yield return new ({self}.Value, ""{typeSymbol.Name}"");");

foreach (var s in $"IEnumerable<KeyValuePair<string, string>> {AttributeExpressionNameTrackerInterface}.{AttributeExpressionNameTrackerInterfaceAccessedNames}()".CreateScope(yields))
yield return s;

yield return $"public override string ToString() => {self}.Value;";
}

private static IEnumerable<string> YieldSelector((bool IsUnknown, DynamoDbDataMember DDB, string IfBranchAlias, string DbRef, string NameRef, string AttributeReference, string AttributeInterfaceName) x)
{

if (x.IsUnknown)
{
var scope = $@"if (new KeyValuePair<string, string>(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {x.IfBranchAlias} && {SetFieldName}.Add({x.IfBranchAlias}))"
.CreateScope($"yield return {x.IfBranchAlias};")
.Concat($"foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{AttributeExpressionNameTrackerInterfaceAccessedNames}())".CreateScope("yield return x;"));
return $"if ({x.NameRef}.IsValueCreated)".CreateScope(scope);
}
else
{
return $@"if ({x.NameRef}.IsValueCreated && new KeyValuePair<string, string>(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {x.IfBranchAlias} && {SetFieldName}.Add({x.IfBranchAlias}))".CreateScope($"yield return {x.IfBranchAlias};");
}
}

private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func<ITypeSymbol, ImmutableArray<DynamoDbDataMember>> fn, MarshallerOptions options)
{
var dataMembers = fn(typeSymbol)
.Select(x => (
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
Expand All @@ -89,6 +109,6 @@ private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func<ITypeSymbol
internal static (string method, string typeName) RootSignature(ITypeSymbol typeSymbol)
{
var typeName = TypeName(typeSymbol);
return ($"public {typeName} {AttributeExpressionNameTrackerMethodName}() => new {typeName}(null);", typeName);
return ($"public {typeName} {AttributeExpressionNameTrackerMethodName}() => new {typeName}(null, new HashSet<KeyValuePair<string ,string>>());", typeName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using DynamoDBGenerator.Attributes;
namespace DynamoDBGenerator.SourceGenerator.Tests.DynamoDBDocumentTests;


[DynamoDBMarshaller(EntityType = typeof(Person), ArgumentType = typeof((string firstName, DateTime timeStamp)), AccessName = "PersonWithTupleArgument")]
[DynamoDBMarshaller(EntityType = typeof(Person))]
[DynamoDBMarshaller(EntityType = typeof(SelfReferencingClass))]
[DynamoDBMarshaller(EntityType = typeof(ClassWithOverriddenAttributeName))]
[DynamoDBMarshaller(EntityType = typeof(InheritedClass))]
[DynamoDBMarshaller(EntityType = typeof(DuplicatedNavigationProperties))]
public partial class ExpressionAttributeTrackerTests
{
[Fact]
Expand All @@ -22,12 +24,12 @@ public void PersonWithTupleArgument_AccessingRootExpressionAttributeValue_Should
{
var valueTracker = PersonWithTupleArgument.AttributeExpressionValueTracker();
var tracker = valueTracker as IAttributeExpressionValueTracker<(string firstName, DateTime timeStamp)>;

var act = () => valueTracker.ToString();
act.Should().NotThrow();
tracker.ToString().Should().Be(":p1");
}

[Fact]
public void PersonWithTupleArgument_AccessingNestedExpressionAttributeName_ShouldNotThrow()
{
Expand All @@ -38,7 +40,7 @@ public void PersonWithTupleArgument_AccessingNestedExpressionAttributeName_Shoul
act.Should().NotThrow();
nameTracker.Address.ToString().Should().Be("#Address");
}

[Fact]
public void PersonWithTupleArgument_Tuple_CanBeParameterized()
{
Expand All @@ -64,6 +66,53 @@ public void PersonWithTupleArgument_Tuple_CanBeParameterized()
});
}


[Fact]
public void SelfReference_AttributeNames_EnsureUniquness()
{
var nametracker = SelfReferencingClassMarshaller.AttributeExpressionNameTracker();
var field1 = nametracker.Self.Self.Self.Self.Field1;
var field2 = nametracker.Self.Self.Self.Field2;

(nametracker as IAttributeExpressionNameTracker)
.AccessedNames()
.Should()
.BeEquivalentTo(new KeyValuePair<string, string>[] {
new KeyValuePair<string ,string>("#Self", "Self"),
new KeyValuePair<string ,string>("#Field1", "Field1"),
new KeyValuePair<string ,string>("#Field2", "Field2")
});

field1.Should().Be("#Self.#Self.#Self.#Self.#Field1");
field2.Should().Be("#Self.#Self.#Self.#Field2");
}
[Fact]
public void DuplicatedNavigationProperties_AttributeNames_EnsureUniquness()
{
var nametracker = DuplicatedNavigationPropertiesMarshaller.AttributeExpressionNameTracker();
var field1 = nametracker.Person1.CreatedAt;
var field2 = nametracker.Person2.CreatedAt;
var field3 = nametracker.Person1.Address.Name;
var field4 = nametracker.Person2.Address.Street.Name;

(nametracker as IAttributeExpressionNameTracker)
.AccessedNames()
.Should()
.BeEquivalentTo(new KeyValuePair<string, string>[] {
new KeyValuePair<string ,string>("#CreatedAt", "CreatedAt"),
new KeyValuePair<string ,string>("#Person1", "Person1"),
new KeyValuePair<string ,string>("#Person2", "Person2"),
new KeyValuePair<string ,string>("#Address", "Address"),
new KeyValuePair<string ,string>("#Name", "Name"),
new KeyValuePair<string ,string>("#Street", "Street")
});

field1.Should().Be("#Person1.#CreatedAt");
field2.Should().Be("#Person2.#CreatedAt");
field3.Should().Be("#Person1.#Address.#Name");
field4.Should().Be("#Person2.#Address.#Street.#Name");
}

[Theory]
[InlineData(5)]
[InlineData(10)]
Expand Down Expand Up @@ -187,7 +236,7 @@ int count

public class InheritedClass : ClassWithOverriddenAttributeName
{

}

public class ClassWithOverriddenAttributeName
Expand All @@ -196,6 +245,12 @@ public class ClassWithOverriddenAttributeName
public string Foo { get; set; } = null!;
}


public class DuplicatedNavigationProperties
{
public Person Person1 { get; set; } = null!;
public Person Person2 { get; set; } = null!;
}
public class SelfReferencingClass
{
public string Field1 { get; set; } = null!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ public void ToAttributeExpression_SetStatus()
{
{"#Id", "Id"},
{"#AdoptionStatus", "AdoptionStatus"},
{"#Metadata.#StatusSetAt", "StatusSetAt"}
{"#Metadata", "Metadata"},
{"#StatusSetAt", "StatusSetAt"}
});

attributeExpression.Values.Should().BeEquivalentTo(new Dictionary<string, AttributeValue>
Expand Down
Loading