Skip to content

Commit 18ed27b

Browse files
committed
fix bug in class with generic type
1 parent 9b3cb07 commit 18ed27b

30 files changed

+483
-175
lines changed

Directory.Packages.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
99
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="[4.4.0]" />
1010
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
11-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
11+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
1212
<PackageVersion Include="MinVer" Version="6.0.0" />
13-
<PackageVersion Include="System.Text.Json" Version="9.0.9" />
14-
<PackageVersion Include="Verify.Xunit" Version="30.19.1" />
13+
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
14+
<PackageVersion Include="Verify.Xunit" Version="31.0.4" />
1515
<PackageVersion Include="xunit" Version="2.9.3" />
1616
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
1717
</ItemGroup>

src/Equatable.SourceGenerator/EquatableGenerator.cs

Lines changed: 34 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
using System.Reflection;
2-
using System.Xml.Linq;
3-
41
using Equatable.SourceGenerator.Models;
52

63
using Microsoft.CodeAnalysis;
@@ -12,53 +9,36 @@ namespace Equatable.SourceGenerator;
129
[Generator]
1310
public class EquatableGenerator : IIncrementalGenerator
1411
{
15-
private static SymbolDisplayFormat FullyQualifiedNullableFormat = SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
12+
private static readonly SymbolDisplayFormat FullyQualifiedNullableFormat = SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
13+
private static readonly SymbolDisplayFormat NameAndNamespaces = new(SymbolDisplayGlobalNamespaceStyle.Omitted, SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, SymbolDisplayGenericsOptions.None);
1614

1715
public void Initialize(IncrementalGeneratorInitializationContext context)
1816
{
19-
var provider = context.SyntaxProvider.ForAttributeWithMetadataName(
20-
fullyQualifiedMetadataName: "Equatable.Attributes.EquatableAttribute",
21-
predicate: SyntacticPredicate,
22-
transform: SemanticTransform
23-
)
24-
.Where(static context => context is not null);
25-
26-
// Emit the diagnostics, if needed
27-
var diagnostics = provider
28-
.Select(static (item, _) => item?.Diagnostics)
29-
.Where(static item => item?.Count > 0);
30-
31-
context.RegisterSourceOutput(diagnostics, ReportDiagnostic);
17+
var provider = context.SyntaxProvider
18+
.ForAttributeWithMetadataName(
19+
fullyQualifiedMetadataName: "Equatable.Attributes.EquatableAttribute",
20+
predicate: SyntacticPredicate,
21+
transform: SemanticTransform
22+
)
23+
.Where(static context => context is not null)
24+
.WithTrackingName("EquatableAttribute");
3225

3326
// output code
3427
var entityClasses = provider
35-
.Select(static (item, _) => item?.EntityClass)
3628
.Where(static item => item is not null);
3729

3830
context.RegisterSourceOutput(entityClasses, Execute);
3931
}
4032

41-
private static void ReportDiagnostic(SourceProductionContext context, EquatableArray<Diagnostic>? diagnostics)
42-
{
43-
if (diagnostics == null)
44-
return;
45-
46-
foreach (var diagnostic in diagnostics)
47-
context.ReportDiagnostic(diagnostic);
48-
}
4933

5034
private static void Execute(SourceProductionContext context, EquatableClass? entityClass)
5135
{
5236
if (entityClass == null)
5337
return;
5438

55-
var qualifiedName = entityClass.EntityNamespace is null
56-
? entityClass.EntityName
57-
: $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
58-
5939
var source = EquatableWriter.Generate(entityClass);
6040

61-
context.AddSource($"{qualifiedName}.Equatable.g.cs", source);
41+
context.AddSource(entityClass.FileName, source);
6242
}
6343

6444

@@ -69,16 +49,16 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
6949
|| (syntaxNode is StructDeclarationSyntax structDeclaration && !structDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword));
7050
}
7151

72-
private static EquatableContext? SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
52+
private static EquatableClass? SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
7353
{
7454
if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
7555
return null;
7656

77-
var diagnostics = new List<Diagnostic>();
78-
79-
var fullyQualified = targetSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
57+
var fullyQualified = targetSymbol.ToDisplayString(FullyQualifiedNullableFormat);
8058
var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
81-
var className = targetSymbol.Name;
59+
var className = targetSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
60+
var qualifiedName = targetSymbol.ToDisplayString(NameAndNamespaces);
61+
var fileName = $"{qualifiedName}.Equatable.g.cs";
8262

8363
// support nested types
8464
var containingTypes = GetContainingTypes(targetSymbol);
@@ -90,7 +70,7 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
9070
var propertySymbols = GetProperties(targetSymbol, baseHashCode == null && baseEquatable == null);
9171

9272
var propertyArray = propertySymbols
93-
.Select(symbol => CreateProperty(diagnostics, symbol))
73+
.Select(CreateProperty)
9474
.ToArray() ?? [];
9575

9676
// the seed value of the hash code method
@@ -106,10 +86,11 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
10686
foreach (var property in propertyArray)
10787
seedHash = (seedHash * HashFactor) + GetFNVHashCode(property.PropertyName);
10888

109-
var entity = new EquatableClass(
89+
return new EquatableClass(
11090
FullyQualified: fullyQualified,
11191
EntityNamespace: classNamespace,
11292
EntityName: className,
93+
FileName: fileName,
11394
ContainingTypes: containingTypes,
11495
Properties: propertyArray,
11596
IsRecord: targetSymbol.IsRecord,
@@ -119,8 +100,6 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
119100
IncludeBaseHashMethod: baseHashCode != null || baseEquatable != null,
120101
SeedHash: seedHash
121102
);
122-
123-
return new EquatableContext(entity, diagnostics.ToArray());
124103
}
125104

126105

@@ -152,10 +131,12 @@ private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol targe
152131
return properties.Values;
153132
}
154133

155-
private static EquatableProperty CreateProperty(List<Diagnostic> diagnostics, IPropertySymbol propertySymbol)
134+
private static EquatableProperty CreateProperty(IPropertySymbol propertySymbol)
156135
{
157136
var propertyType = propertySymbol.Type.ToDisplayString(FullyQualifiedNullableFormat);
158137
var propertyName = propertySymbol.Name;
138+
var isValueType = propertySymbol.Type.IsValueType;
139+
var defaultComparer = isValueType ? ComparerTypes.ValueType : ComparerTypes.Default;
159140

160141
// look for custom equality
161142
var attributes = propertySymbol.GetAttributes();
@@ -164,7 +145,7 @@ private static EquatableProperty CreateProperty(List<Diagnostic> diagnostics, IP
164145
return new EquatableProperty(
165146
propertyName,
166147
propertyType,
167-
ComparerTypes.Default);
148+
defaultComparer);
168149
}
169150

170151
// search for known attribute
@@ -175,15 +156,13 @@ private static EquatableProperty CreateProperty(List<Diagnostic> diagnostics, IP
175156
if (!comparerType.HasValue)
176157
continue;
177158

178-
var diagnostic = ValidateComparer(propertySymbol, comparerType);
179-
if (diagnostic != null)
159+
var isValid = ValidateComparer(propertySymbol, comparerType);
160+
if (!isValid)
180161
{
181-
diagnostics.Add(diagnostic);
182-
183162
return new EquatableProperty(
184163
propertyName,
185164
propertyType,
186-
ComparerTypes.Default);
165+
defaultComparer);
187166
}
188167

189168
return new EquatableProperty(
@@ -197,64 +176,28 @@ private static EquatableProperty CreateProperty(List<Diagnostic> diagnostics, IP
197176
return new EquatableProperty(
198177
propertyName,
199178
propertyType,
200-
ComparerTypes.Default);
179+
defaultComparer);
201180
}
202181

203-
private static Diagnostic? ValidateComparer(IPropertySymbol propertySymbol, ComparerTypes? comparerType)
182+
private static bool ValidateComparer(IPropertySymbol propertySymbol, ComparerTypes? comparerType)
204183
{
205184
// don't need to validate these types
206185
if (comparerType is null or ComparerTypes.Default or ComparerTypes.Reference or ComparerTypes.Custom)
207-
return null;
186+
return true;
208187

209188
if (comparerType == ComparerTypes.String)
210-
{
211-
if (IsString(propertySymbol.Type))
212-
return null;
213-
214-
return Diagnostic.Create(
215-
DiagnosticDescriptors.InvalidStringEqualityAttributeUsage,
216-
propertySymbol.Locations.FirstOrDefault(),
217-
propertySymbol.Name
218-
);
219-
}
189+
return IsString(propertySymbol.Type);
220190

221191
if (comparerType == ComparerTypes.Dictionary)
222-
{
223-
if (propertySymbol.Type.AllInterfaces.Any(IsDictionary))
224-
return null;
225-
226-
return Diagnostic.Create(
227-
DiagnosticDescriptors.InvalidDictionaryEqualityAttributeUsage,
228-
propertySymbol.Locations.FirstOrDefault(),
229-
propertySymbol.Name
230-
);
231-
}
192+
return propertySymbol.Type.AllInterfaces.Any(IsDictionary);
232193

233194
if (comparerType == ComparerTypes.HashSet)
234-
{
235-
if (propertySymbol.Type.AllInterfaces.Any(IsEnumerable))
236-
return null;
237-
238-
return Diagnostic.Create(
239-
DiagnosticDescriptors.InvalidHashSetEqualityAttributeUsage,
240-
propertySymbol.Locations.FirstOrDefault(),
241-
propertySymbol.Name
242-
);
243-
}
195+
return propertySymbol.Type.AllInterfaces.Any(IsEnumerable);
244196

245197
if (comparerType == ComparerTypes.Sequence)
246-
{
247-
if (propertySymbol.Type.AllInterfaces.Any(IsEnumerable))
248-
return null;
198+
return propertySymbol.Type.AllInterfaces.Any(IsEnumerable);
249199

250-
return Diagnostic.Create(
251-
DiagnosticDescriptors.InvalidSequenceEqualityAttributeUsage,
252-
propertySymbol.Locations.FirstOrDefault(),
253-
propertySymbol.Name
254-
);
255-
}
256-
257-
return null;
200+
return true;
258201
}
259202

260203

src/Equatable.SourceGenerator/EquatableWriter.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ private static void GenerateEquatable(IndentedStringBuilder codeBuilder, Equatab
187187
.Append(entityProperty.PropertyName)
188188
.Append(")");
189189

190+
break;
191+
case ComparerTypes.ValueType:
192+
codeBuilder
193+
.Append(" ")
194+
.Append(entityProperty.PropertyName)
195+
.Append(" == other.")
196+
.Append(entityProperty.PropertyName);
197+
190198
break;
191199
default:
192200
codeBuilder
@@ -457,6 +465,12 @@ private static void GenerateHashCode(IndentedStringBuilder codeBuilder, Equatabl
457465
.Append(entityProperty.PropertyName)
458466
.AppendLine("!);");
459467
break;
468+
case ComparerTypes.ValueType:
469+
codeBuilder
470+
.Append("hashCode = (hashCode * -1521134295) + ")
471+
.Append(entityProperty.PropertyName)
472+
.AppendLine(".GetHashCode();");
473+
break;
460474
default:
461475
codeBuilder
462476
.Append("hashCode = (hashCode * -1521134295) + ")

src/Equatable.SourceGenerator/Models/ComparerTypes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ public enum ComparerTypes
88
Reference,
99
Sequence,
1010
String,
11+
ValueType,
1112
Custom
1213
}

src/Equatable.SourceGenerator/Models/EquatableClass.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public record EquatableClass(
44
string FullyQualified,
55
string EntityNamespace,
66
string EntityName,
7+
string FileName,
78
EquatableArray<ContainingClass> ContainingTypes,
89
EquatableArray<EquatableProperty> Properties,
910
bool IsRecord,

src/Equatable.SourceGenerator/Models/EquatableContext.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
3+
namespace Equatable.Entities;
4+
5+
public interface ITimeEntry
6+
{
7+
DateTime EntryDate { get; set; }
8+
9+
DateTime? AmTimeIn { get; set; }
10+
DateTime? AmTimeOut { get; set; }
11+
12+
DateTime? PmTimeIn { get; set; }
13+
DateTime? PmTimeOut { get; set; }
14+
15+
decimal? OtherHours { get; set; }
16+
17+
decimal? TotalHours { get; set; }
18+
19+
DateTimeOffset Created { get; set; }
20+
DateTimeOffset Updated { get; set; }
21+
}

test/Equatable.Entities/Role.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System;
2-
31
using Equatable.Attributes;
42

53
namespace Equatable.Entities;

test/Equatable.Entities/StatusReadOnly.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System;
2-
31
using Equatable.Attributes;
42

53
namespace Equatable.Entities;

test/Equatable.Entities/StatusRecordList.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32

43
using Equatable.Attributes;

0 commit comments

Comments
 (0)