Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/Vogen/GenerateCodeForHashCodes.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Analyzer.Utilities.Extensions;

namespace Vogen;

public static class GenerateCodeForHashCodes
Expand All @@ -11,6 +13,13 @@ public static string GenerateGetHashCodeForAClass(VoWorkItem item)

string itemUnderlyingType = item.UnderlyingTypeFullNameWithGlobalAlias;

// When the underlying type is a nullable value type (e.g. ushort?), EqualityComparer<T>.Default.GetHashCode
// has [DisallowNull] on its parameter in .NET 10+, causing CS8607. Use null-safe approach instead.
// Wrap the expression in parentheses to ensure correct precedence with the ^ operator.
string hashCodeExpression = item.UnderlyingType.IsNullableValueType()
? "(Value is null ? 0 : Value.GetHashCode())"
: $"global::System.Collections.Generic.EqualityComparer<{itemUnderlyingType}>.Default.GetHashCode(Value)";

return
$$"""

Expand All @@ -20,7 +29,7 @@ public static string GenerateGetHashCodeForAClass(VoWorkItem item)
{
global::System.Int32 hash = (global::System.Int32) 2166136261;
hash = (hash * 16777619) ^ GetType().GetHashCode();
hash = (hash * 16777619) ^ global::System.Collections.Generic.EqualityComparer<{{itemUnderlyingType}}>.Default.GetHashCode(Value);
hash = (hash * 16777619) ^ {{hashCodeExpression}};
return hash;
}
}
Expand All @@ -36,12 +45,18 @@ public static string GenerateForAStruct(VoWorkItem item)

string itemUnderlyingType = item.UnderlyingTypeFullNameWithGlobalAlias;

// When the underlying type is a nullable value type (e.g. ushort?), EqualityComparer<T>.Default.GetHashCode
// has [DisallowNull] on its parameter in .NET 10+, causing CS8607. Use null-safe approach instead.
string hashCodeExpression = item.UnderlyingType.IsNullableValueType()
? "Value is null ? 0 : Value.GetHashCode()"
: $"global::System.Collections.Generic.EqualityComparer<{itemUnderlyingType}>.Default.GetHashCode(Value)";

return
$$"""

public readonly override global::System.Int32 GetHashCode()
{
return global::System.Collections.Generic.EqualityComparer<{{itemUnderlyingType}}>.Default.GetHashCode(Value);
return {{hashCodeExpression}};
}
""";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Threading.Tasks;
using Vogen;

namespace SnapshotTests.BugFixes;

// See https://github.com/SteveDunn/Vogen/issues/914
// ValueObject with a nullable value type underlying (e.g. ushort?) generated CS8607 because
// EqualityComparer<T>.Default.GetHashCode has [DisallowNull] in .NET 10+.
public class Bug914Tests
{
[Theory]
[InlineData("partial struct")]
[InlineData("readonly partial struct")]
[InlineData("partial class")]
[InlineData("partial record class")]
[InlineData("partial record struct")]
[InlineData("readonly partial record struct")]
public async Task NullableValueType_underlying_generates_null_safe_GetHashCode(string type)
{
var source = $$"""
using Vogen;

namespace ConsumerTests;

[ValueObject<ushort?>]
internal {{type}} Speed
{
private const ushort UnknownSpeed = 255;

private static ushort? NormalizeInput(ushort? input) =>
input == UnknownSpeed ? null : input;
}
""";

await new SnapshotRunner<ValueObjectGenerator>()
.WithSource(source)
.CustomizeSettings(s => s.UseHashedParameters(type))
.RunOnAllFrameworks();
}
}
Loading
Loading