Skip to content

Commit

Permalink
mostly working
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Sep 4, 2024
1 parent 00ebf18 commit bdab79e
Show file tree
Hide file tree
Showing 37 changed files with 1,606 additions and 163 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,13 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml

# Custom
/artifacts
/Tools
*.GhostDoc.xml
coverage.xml
coverage.opencover.xml
*.received.txt
.idea
_site
11 changes: 2 additions & 9 deletions Equatable.Generator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9ABD9B14
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equatable.Generator.Tests", "test\Equatable.Generator.Tests\Equatable.Generator.Tests.csproj", "{FC77B226-0FC3-4AD4-8364-761D5FC419E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equatable.SourceGenerator.Tests", "test\Equatable.SourceGenerator.Tests\Equatable.SourceGenerator.Tests.csproj", "{75E86899-4DD9-4493-8EE4-CF8237F2539D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{EA286CB8-AC01-452B-9092-97DCE3817093}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equatable.Entities", "test\Equatable.Entities\Equatable.Entities.csproj", "{E1C35715-F118-433E-B456-11FD4B7B3079}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equatable.Entities", "test\Equatable.Entities\Equatable.Entities.csproj", "{E1C35715-F118-433E-B456-11FD4B7B3079}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equatable.Comparers", "src\Equatable.Comparers\Equatable.Comparers.csproj", "{E8A054EF-222A-4CD8-9177-643050501FB1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equatable.Comparers", "src\Equatable.Comparers\Equatable.Comparers.csproj", "{E8A054EF-222A-4CD8-9177-643050501FB1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -42,10 +40,6 @@ Global
{FC77B226-0FC3-4AD4-8364-761D5FC419E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC77B226-0FC3-4AD4-8364-761D5FC419E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC77B226-0FC3-4AD4-8364-761D5FC419E5}.Release|Any CPU.Build.0 = Release|Any CPU
{75E86899-4DD9-4493-8EE4-CF8237F2539D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75E86899-4DD9-4493-8EE4-CF8237F2539D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75E86899-4DD9-4493-8EE4-CF8237F2539D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75E86899-4DD9-4493-8EE4-CF8237F2539D}.Release|Any CPU.Build.0 = Release|Any CPU
{E1C35715-F118-433E-B456-11FD4B7B3079}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1C35715-F118-433E-B456-11FD4B7B3079}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1C35715-F118-433E-B456-11FD4B7B3079}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -60,7 +54,6 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FC77B226-0FC3-4AD4-8364-761D5FC419E5} = {EA286CB8-AC01-452B-9092-97DCE3817093}
{75E86899-4DD9-4493-8EE4-CF8237F2539D} = {EA286CB8-AC01-452B-9092-97DCE3817093}
{E1C35715-F118-433E-B456-11FD4B7B3079} = {EA286CB8-AC01-452B-9092-97DCE3817093}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
13 changes: 1 addition & 12 deletions src/Equatable.Generator/Attributes/EquatableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,4 @@ namespace Equatable.Attributes;

[Conditional("EQUATABLE_GENERATOR")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class EquatableAttribute : Attribute
{
/// <summary>
/// Only members marked with equality attributes will be generated for Equal and GetHashCode.
/// </summary>
public bool Explicit { get; set; }

/// <summary>
/// Equal and GetHashCode generation do not consider members of base classes.
/// </summary>
public bool IgnoreInherited { get; set; }
}
public class EquatableAttribute : Attribute;
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="[4.3.1]" PrivateAssets="all" />
<PackageReference Include="Polyfill" Version="6.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
187 changes: 179 additions & 8 deletions src/Equatable.SourceGenerator/EquatableGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Xml.Linq;

using Equatable.SourceGenerator.Models;

using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -72,18 +74,46 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
var className = targetSymbol.Name;

var propertySymbols = GetProperties(targetSymbol);
var baseHashCode = GetBaseHashCodeMethod(targetSymbol);
var baseEquals = GetBaseEqualsMethod(targetSymbol);
var baseEquatable = GetBaseEquatableType(targetSymbol);

var propertySymbols = GetProperties(targetSymbol, baseHashCode == null && baseEquatable == null);

var propertyArray = propertySymbols
.Select(p => CreateProperty(p))
.Select(CreateProperty)
.ToArray() ?? [];

var entity = new EquatableClass(classNamespace, className, propertyArray);
// the seed value of the hash code method
var seedHash = 0;

if (baseHashCode != null)
seedHash = (seedHash * HashFactor) + GetFNVHashCode(baseHashCode.ContainingSymbol.Name);
else if (baseEquatable != null)
seedHash = (seedHash * HashFactor) + GetFNVHashCode(baseEquatable.Name);
else if (baseEquals != null)
seedHash = (seedHash * HashFactor) + GetFNVHashCode(baseEquals.ContainingSymbol.Name);

foreach (var property in propertyArray)
seedHash = (seedHash * HashFactor) + GetFNVHashCode(property.PropertyName);

var entity = new EquatableClass(
EntityNamespace: classNamespace,
EntityName: className,
Properties: propertyArray,
IsRecord: targetSymbol.IsRecord,
IsValueType: targetSymbol.IsValueType,
IsSealed: targetSymbol.IsSealed,
IncludeBaseEqualsMethod: baseEquals != null || baseEquatable != null,
IncludeBaseHashMethod: baseHashCode != null || baseEquatable != null,
SeedHash: seedHash
);

return new EquatableContext(entity, null);
}


private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol targetSymbol)
private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol targetSymbol, bool includeBaseProperties = true)
{
var properties = new Dictionary<string, IPropertySymbol>();

Expand All @@ -102,6 +132,9 @@ private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol targe
foreach (var propertySymbol in propertySymbols)
properties.Add(propertySymbol.Name, propertySymbol);

if (!includeBaseProperties)
break;

currentSymbol = currentSymbol.BaseType;
}

Expand All @@ -115,7 +148,7 @@ private static EquatableProperty CreateProperty(IPropertySymbol propertySymbol)

// look for custom equality
var attributes = propertySymbol.GetAttributes();
if (attributes == null || attributes.Length == 0)
if (attributes.Length == 0)
{
return new EquatableProperty(
propertyName,
Expand Down Expand Up @@ -148,11 +181,10 @@ private static EquatableProperty CreateProperty(IPropertySymbol propertySymbol)

private static (ComparerTypes? comparerType, string? comparerName, string? comparerInstance) GetComparer(AttributeData? attribute)
{
// known attributes
if (attribute == null || attribute.AttributeClass is not { ContainingNamespace: { Name: "Attributes", ContainingNamespace.Name: "Equatable" } })
if (!IsKnownAttribute(attribute))
return (null, null, null);

var className = attribute.AttributeClass?.Name;
var className = attribute?.AttributeClass?.Name;

return className switch
{
Expand Down Expand Up @@ -234,4 +266,143 @@ private static bool IsIncluded(IPropertySymbol propertySymbol)

return !propertySymbol.IsIndexer && propertySymbol.DeclaredAccessibility == Accessibility.Public;
}

private static bool IsKnownAttribute(AttributeData? attribute)
{
if (attribute == null)
return false;

return attribute.AttributeClass is
{
ContainingNamespace:
{
Name: "Attributes",
ContainingNamespace.Name: "Equatable"
}
};

}

private static bool IsValueType(INamedTypeSymbol targetSymbol)
{
return targetSymbol is
{
Name: nameof(ValueType) or nameof(Object),
ContainingNamespace.Name: "System"
};
}


private static IMethodSymbol? GetBaseHashCodeMethod(INamedTypeSymbol targetSymbol)
{
// don't use for value types
if (targetSymbol.BaseType == null)
return null;

var currentSymbol = targetSymbol.BaseType;

// check all base types for GetHashCode method override
while (currentSymbol != null)
{
// stop at ValueType
if (IsValueType(currentSymbol))
return null;

var methodSymbol = currentSymbol
.GetMembers(nameof(GetHashCode))
.OfType<IMethodSymbol>()
.FirstOrDefault(method => method.IsOverride
&& method.DeclaredAccessibility == Accessibility.Public
&& !method.IsStatic
&& method.Parameters.Length == 0
&& method.ReturnType.SpecialType == SpecialType.System_Int32
&& !method.IsAbstract
);

if (methodSymbol != null)
return methodSymbol;

currentSymbol = currentSymbol.BaseType;
}

return null;
}

private static IMethodSymbol? GetBaseEqualsMethod(INamedTypeSymbol targetSymbol)
{
// don't use for value types
if (targetSymbol.BaseType == null)
return null;

var currentSymbol = targetSymbol.BaseType;

// check all base types for Equals method override
while (currentSymbol != null)
{
// stop at ValueType
if (IsValueType(currentSymbol))
return null;

var methodSymbol = currentSymbol
.GetMembers(nameof(Equals))
.OfType<IMethodSymbol>()
.FirstOrDefault(method => method.IsOverride
&& method.DeclaredAccessibility == Accessibility.Public
&& !method.IsStatic
&& method.Parameters.Length == 1
&& method.ReturnType.SpecialType == SpecialType.System_Boolean
&& method.Parameters[0].Type.SpecialType == SpecialType.System_Object
&& !method.IsAbstract
);

if (methodSymbol != null)
return methodSymbol;

currentSymbol = currentSymbol.BaseType;
}

return null;
}

private static INamedTypeSymbol? GetBaseEquatableType(INamedTypeSymbol targetSymbol)
{
if (targetSymbol.BaseType == null)
return null;

var currentSymbol = targetSymbol.BaseType;

// check all base types for Equals method override
while (currentSymbol != null)
{
// stop at ValueType
if (IsValueType(currentSymbol))
return null;

var attributes = currentSymbol.GetAttributes();
if (attributes.Length > 0 && attributes.Any(a => IsKnownAttribute(a) && a.AttributeClass?.Name == "EquatableAttribute"))
{
return currentSymbol;
}

currentSymbol = currentSymbol.BaseType;
}

return null;

}


private const int HashFactor = -1521134295;

private const int FnvOffsetBias = unchecked((int)2166136261);
private const int FnvPrime = 16777619;

private static int GetFNVHashCode(string text)
{
var hashCode = FnvOffsetBias;
for (int i = 0; i < text.Length; i++)
hashCode = unchecked((hashCode ^ text[i]) * FnvPrime);

return hashCode;
}
}
Loading

0 comments on commit bdab79e

Please sign in to comment.