Skip to content

Commit b76a496

Browse files
authored
Fix NameTracker bug (#79)
1 parent 6641d8b commit b76a496

File tree

3 files changed

+92
-16
lines changed

3 files changed

+92
-16
lines changed

src/DynamoDBGenerator.SourceGenerator/Generations/AttributeExpressionName.cs

+31-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public static class AttributeExpressionName
99
{
1010

1111
private const string ConstructorAttributeName = "nameRef";
12+
private const string ConstructorSetName = "set";
13+
private const string SetFieldName = "___set___";
1214

1315
private static readonly Func<ITypeSymbol, string> TypeName = TypeExtensions.SuffixedTypeSymbolNameFactory("Names", SymbolEqualityComparer.Default);
1416
internal static IEnumerable<string> CreateClasses(IEnumerable<DynamoDBMarshallerArguments> arguments, Func<ITypeSymbol, ImmutableArray<DynamoDbDataMember>> getDynamoDbProperties, MarshallerOptions options)
@@ -22,7 +24,7 @@ internal static IEnumerable<string> CreateClasses(IEnumerable<DynamoDBMarshaller
2224
}
2325
private static IEnumerable<string> TypeContent(
2426
ITypeSymbol typeSymbol,
25-
(bool IsUnknown, DynamoDbDataMember DDB, string NameRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers,
27+
(bool IsUnknown, DynamoDbDataMember DDB, string IfBranchAlias, string DbRef, string NameRef, string AttributeReference, string AttributeInterfaceName)[] dataMembers,
2628
string structName)
2729
{
2830
const string self = "_self";
@@ -31,20 +33,21 @@ private static IEnumerable<string> TypeContent(
3133
{
3234
var ternaryExpressionName = $"{ConstructorAttributeName} is null ? {@$"""#{x.DDB.AttributeName}"""}: {@$"$""{{{ConstructorAttributeName}}}.#{x.DDB.AttributeName}"""}";
3335
return x.IsUnknown
34-
? $"_{x.DDB.DataMember.Name} = new (() => new {x.AttributeReference}({ternaryExpressionName}));"
36+
? $"{x.NameRef} = new (() => new {x.AttributeReference}({ternaryExpressionName}, {ConstructorSetName}));"
3537
: $"{x.NameRef} = new (() => {ternaryExpressionName});";
3638
})
39+
.Append($"{SetFieldName} = {ConstructorSetName};")
3740
.Append($@"{self} = new(() => {ConstructorAttributeName} ?? throw new NotImplementedException(""Root element AttributeExpressionName reference.""));");
3841
39-
foreach (var fieldAssignment in $"public {structName}(string? {ConstructorAttributeName})".CreateScope(constructorFieldAssignments))
42+
foreach (var fieldAssignment in $"public {structName}(string? {ConstructorAttributeName}, HashSet<KeyValuePair<string, string>> {ConstructorSetName})".CreateScope(constructorFieldAssignments))
4043
yield return fieldAssignment;
4144
4245
foreach (var fieldDeclaration in dataMembers)
4346
{
4447
if (fieldDeclaration.IsUnknown)
4548
{
46-
yield return $"private readonly Lazy<{fieldDeclaration.AttributeReference}> _{fieldDeclaration.DDB.DataMember.Name};";
47-
yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => _{fieldDeclaration.DDB.DataMember.Name}.Value;";
49+
yield return $"private readonly Lazy<{fieldDeclaration.AttributeReference}> {fieldDeclaration.NameRef};";
50+
yield return $"public {fieldDeclaration.AttributeReference} {fieldDeclaration.DDB.DataMember.Name} => {fieldDeclaration.NameRef}.Value;";
4851
}
4952
else
5053
{
@@ -54,25 +57,42 @@ private static IEnumerable<string> TypeContent(
5457
}
5558
}
5659
yield return $"private readonly Lazy<string> {self};";
60+
yield return $"private readonly HashSet<KeyValuePair<string, string>> {SetFieldName};";
5761

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

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

6869
yield return $"public override string ToString() => {self}.Value;";
6970
}
71+
72+
private static IEnumerable<string> YieldSelector((bool IsUnknown, DynamoDbDataMember DDB, string IfBranchAlias, string DbRef, string NameRef, string AttributeReference, string AttributeInterfaceName) x)
73+
{
74+
75+
if (x.IsUnknown)
76+
{
77+
var scope = $@"if (new KeyValuePair<string, string>(""{x.DbRef}"", ""{x.DDB.AttributeName}"") is var {x.IfBranchAlias} && {SetFieldName}.Add({x.IfBranchAlias}))"
78+
.CreateScope($"yield return {x.IfBranchAlias};")
79+
.Concat($"foreach (var x in ({x.DDB.DataMember.Name} as {x.AttributeInterfaceName}).{AttributeExpressionNameTrackerInterfaceAccessedNames}())".CreateScope("yield return x;"));
80+
return $"if ({x.NameRef}.IsValueCreated)".CreateScope(scope);
81+
}
82+
else
83+
{
84+
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};");
85+
}
86+
}
87+
7088
private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func<ITypeSymbol, ImmutableArray<DynamoDbDataMember>> fn, MarshallerOptions options)
7189
{
7290
var dataMembers = fn(typeSymbol)
7391
.Select(x => (
7492
IsUnknown: !options.IsConvertable(x.DataMember.Type) && x.DataMember.Type.TypeIdentifier() is UnknownType,
7593
DDB: x,
94+
IfBranchAlias: $"__{x.DataMember.Name}__",
95+
DbRef: $"#{x.AttributeName}",
7696
NameRef: $"_{x.DataMember.Name}NameRef",
7797
AttributeReference: TypeName(x.DataMember.Type),
7898
AttributeInterfaceName: AttributeExpressionNameTrackerInterface
@@ -89,6 +109,6 @@ private static CodeFactory CreateStruct(ITypeSymbol typeSymbol, Func<ITypeSymbol
89109
internal static (string method, string typeName) RootSignature(ITypeSymbol typeSymbol)
90110
{
91111
var typeName = TypeName(typeSymbol);
92-
return ($"public {typeName} {AttributeExpressionNameTrackerMethodName}() => new {typeName}(null);", typeName);
112+
return ($"public {typeName} {AttributeExpressionNameTrackerMethodName}() => new {typeName}(null, new HashSet<KeyValuePair<string ,string>>());", typeName);
93113
}
94-
}
114+
}

tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBDocumentTests/ExpressionAttributeTrackerTests.cs

+59-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using DynamoDBGenerator.Attributes;
22
namespace DynamoDBGenerator.SourceGenerator.Tests.DynamoDBDocumentTests;
33

4+
45
[DynamoDBMarshaller(EntityType = typeof(Person), ArgumentType = typeof((string firstName, DateTime timeStamp)), AccessName = "PersonWithTupleArgument")]
56
[DynamoDBMarshaller(EntityType = typeof(Person))]
67
[DynamoDBMarshaller(EntityType = typeof(SelfReferencingClass))]
78
[DynamoDBMarshaller(EntityType = typeof(ClassWithOverriddenAttributeName))]
89
[DynamoDBMarshaller(EntityType = typeof(InheritedClass))]
10+
[DynamoDBMarshaller(EntityType = typeof(DuplicatedNavigationProperties))]
911
public partial class ExpressionAttributeTrackerTests
1012
{
1113
[Fact]
@@ -22,12 +24,12 @@ public void PersonWithTupleArgument_AccessingRootExpressionAttributeValue_Should
2224
{
2325
var valueTracker = PersonWithTupleArgument.AttributeExpressionValueTracker();
2426
var tracker = valueTracker as IAttributeExpressionValueTracker<(string firstName, DateTime timeStamp)>;
25-
27+
2628
var act = () => valueTracker.ToString();
2729
act.Should().NotThrow();
2830
tracker.ToString().Should().Be(":p1");
2931
}
30-
32+
3133
[Fact]
3234
public void PersonWithTupleArgument_AccessingNestedExpressionAttributeName_ShouldNotThrow()
3335
{
@@ -38,7 +40,7 @@ public void PersonWithTupleArgument_AccessingNestedExpressionAttributeName_Shoul
3840
act.Should().NotThrow();
3941
nameTracker.Address.ToString().Should().Be("#Address");
4042
}
41-
43+
4244
[Fact]
4345
public void PersonWithTupleArgument_Tuple_CanBeParameterized()
4446
{
@@ -64,6 +66,53 @@ public void PersonWithTupleArgument_Tuple_CanBeParameterized()
6466
});
6567
}
6668

69+
70+
[Fact]
71+
public void SelfReference_AttributeNames_EnsureUniquness()
72+
{
73+
var nametracker = SelfReferencingClassMarshaller.AttributeExpressionNameTracker();
74+
var field1 = nametracker.Self.Self.Self.Self.Field1;
75+
var field2 = nametracker.Self.Self.Self.Field2;
76+
77+
(nametracker as IAttributeExpressionNameTracker)
78+
.AccessedNames()
79+
.Should()
80+
.BeEquivalentTo(new KeyValuePair<string, string>[] {
81+
new KeyValuePair<string ,string>("#Self", "Self"),
82+
new KeyValuePair<string ,string>("#Field1", "Field1"),
83+
new KeyValuePair<string ,string>("#Field2", "Field2")
84+
});
85+
86+
field1.Should().Be("#Self.#Self.#Self.#Self.#Field1");
87+
field2.Should().Be("#Self.#Self.#Self.#Field2");
88+
}
89+
[Fact]
90+
public void DuplicatedNavigationProperties_AttributeNames_EnsureUniquness()
91+
{
92+
var nametracker = DuplicatedNavigationPropertiesMarshaller.AttributeExpressionNameTracker();
93+
var field1 = nametracker.Person1.CreatedAt;
94+
var field2 = nametracker.Person2.CreatedAt;
95+
var field3 = nametracker.Person1.Address.Name;
96+
var field4 = nametracker.Person2.Address.Street.Name;
97+
98+
(nametracker as IAttributeExpressionNameTracker)
99+
.AccessedNames()
100+
.Should()
101+
.BeEquivalentTo(new KeyValuePair<string, string>[] {
102+
new KeyValuePair<string ,string>("#CreatedAt", "CreatedAt"),
103+
new KeyValuePair<string ,string>("#Person1", "Person1"),
104+
new KeyValuePair<string ,string>("#Person2", "Person2"),
105+
new KeyValuePair<string ,string>("#Address", "Address"),
106+
new KeyValuePair<string ,string>("#Name", "Name"),
107+
new KeyValuePair<string ,string>("#Street", "Street")
108+
});
109+
110+
field1.Should().Be("#Person1.#CreatedAt");
111+
field2.Should().Be("#Person2.#CreatedAt");
112+
field3.Should().Be("#Person1.#Address.#Name");
113+
field4.Should().Be("#Person2.#Address.#Street.#Name");
114+
}
115+
67116
[Theory]
68117
[InlineData(5)]
69118
[InlineData(10)]
@@ -187,7 +236,7 @@ int count
187236

188237
public class InheritedClass : ClassWithOverriddenAttributeName
189238
{
190-
239+
191240
}
192241

193242
public class ClassWithOverriddenAttributeName
@@ -196,6 +245,12 @@ public class ClassWithOverriddenAttributeName
196245
public string Foo { get; set; } = null!;
197246
}
198247

248+
249+
public class DuplicatedNavigationProperties
250+
{
251+
public Person Person1 { get; set; } = null!;
252+
public Person Person2 { get; set; } = null!;
253+
}
199254
public class SelfReferencingClass
200255
{
201256
public string Field1 { get; set; } = null!;

tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBDocumentTests/Marshaller/Tuples/TupleArgumentTests.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ public void ToAttributeExpression_SetStatus()
149149
{
150150
{"#Id", "Id"},
151151
{"#AdoptionStatus", "AdoptionStatus"},
152-
{"#Metadata.#StatusSetAt", "StatusSetAt"}
152+
{"#Metadata", "Metadata"},
153+
{"#StatusSetAt", "StatusSetAt"}
153154
});
154155

155156
attributeExpression.Values.Should().BeEquivalentTo(new Dictionary<string, AttributeValue>

0 commit comments

Comments
 (0)