Skip to content

Commit 6dc8eea

Browse files
committed
Adjustments regarding "SkipEqualityComparison"
* Removed from `UnionAttribute` because it has no effect on regular unions * Added test for non-generic ad hoc union and keyed value object * Added test classes using the new attribute (smoke test) * Updated docs
1 parent 2965c7e commit 6dc8eea

19 files changed

Lines changed: 459 additions & 217 deletions

File tree

CLAUDE.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This is a .NET library providing **Smart Enums**, **Value Objects**, and **Discr
2222

2323
### Core Components
2424
- **`src/Thinktecture.Runtime.Extensions`**: Core library with base interfaces, attributes, and runtime helpers
25-
- Attributes: `SmartEnumAttribute<TKey>`, `SmartEnumAttribute`, `ValueObjectAttribute<TKey>`, `ComplexValueObjectAttribute`, `UnionAttribute<T1,T2,...>` (up to 5 type parameters), `UnionAttribute`, `ObjectFactoryAttribute<T>`
25+
- Attributes: `SmartEnumAttribute<TKey>`, `SmartEnumAttribute`, `ValueObjectAttribute<TKey>`, `ComplexValueObjectAttribute`, `UnionAttribute<T1,T2,...>` (up to 5 type parameters), `AdHocUnionAttribute` (non-generic alternative to UnionAttribute), `UnionAttribute`, `ObjectFactoryAttribute<T>`
2626
- Additional attributes: `KeyMemberEqualityComparerAttribute`, `KeyMemberComparerAttribute`, `MemberEqualityComparerAttribute`, `IgnoreMemberAttribute`, `ValidationErrorAttribute`, `UseDelegateFromConstructorAttribute`, `UnionSwitchMapOverloadAttribute`
2727
- Key interfaces: `ISmartEnum<TKey>`, `IEnum`, `IKeyedObject<TKey>`, `IKeyedValueObject<TKey>`, `IComplexValueObject`, `IValidationError`, `IObjectFactory<T>`
2828
- Utility types: `ValidationError`, `ComparerAccessors`, collection helpers (EmptyDictionary, EmptyLookup, SingleItemReadOnlyDictionary, etc.)
@@ -53,7 +53,8 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
5353
- Supports arithmetic operators (`+`, `-`, `*`, `/`) when configured
5454
- Integrates with serializers (JSON, MessagePack, Newtonsoft.Json)
5555

56-
3. **`AdHocUnionSourceGenerator`**: Generates code for ad-hoc unions annotated with `[Union<T1, T2, ...>]` (up to 5 types)
56+
3. **`AdHocUnionSourceGenerator`**: Generates code for ad-hoc unions annotated with `[Union<T1, T2, ...>]` or `[AdHocUnion(typeof(T1), typeof(T2), ...)]` (up to 5 types)
57+
- Supports both generic and non-generic attribute syntaxes (identical functionality)
5758
- Creates implicit conversion operators for each union member type
5859
- Generates exhaustive `Switch`/`Map` methods for pattern matching
5960
- Provides type-safe value access with `IsT1`, `AsT1` properties and methods
@@ -147,16 +148,22 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
147148
**Common settings**:
148149
- `SkipFactoryMethods`: Skip generation of `Create`, `TryCreate`, `Validate`
149150
- `SkipIParsable`, `SkipIComparable`, `SkipIFormattable`: Skip interface implementations
151+
- `SkipEqualityComparison`: Skip generation of equality members (`Equals`, `GetHashCode`, `==`, `!=`, `IEquatable<T>`) - also sets `ComparisonOperators` and `EqualityComparisonOperators` to `None`
150152
- Validation: Implement `ValidateFactoryArguments` (preferred) or `ValidateConstructorArguments`
151153

152154
#### 3. Discriminated Unions
153-
**Ad-hoc Unions**: `[Union<T1, T2>]` through `[Union<T1, T2, T3, T4, T5>]`
155+
**Ad-hoc Unions**: `[Union<T1, T2>]` through `[Union<T1, T2, T3, T4, T5>]` OR `[AdHocUnion(typeof(T1), typeof(T2), ...)]`
154156
- Simple combination of 2-5 types without inheritance
157+
- Two syntaxes available:
158+
- **Generic** (recommended): `[Union<string, int>]` - simpler syntax for most cases
159+
- **Non-generic**: `[AdHocUnion(typeof(string), typeof(int))]` - use when generic attribute limitations are hit (e.g., nullable reference types in generic collections like `List<string?>`, or other C# generic attribute constraints)
160+
- Both syntaxes generate identical functionality and support the same customization options
155161
- Generates implicit conversion operators from each type to union (configurable via `ConversionFromValue`)
156162
- Type checking: `IsT1`, `IsT2`, etc. properties
157163
- Value access: `AsT1`, `AsT2`, etc. properties (throws if wrong type)
158164
- Configurable member names: `T1Name`, `T2Name`, etc.
159165
- Nullable reference types: `T1IsNullableReferenceType`, `T2IsNullableReferenceType`, etc.
166+
- `SkipEqualityComparison`: Skip generation of equality members (`Equals`, `GetHashCode`, `==`, `!=`, `IEquatable<T>`) for ad-hoc unions only
160167
- Supports `Switch`/`Map` methods for exhaustive pattern matching
161168

162169
**Regular (Inheritance-based) Unions**: `[Union]`

docs

Submodule docs updated from 4c18540 to ed21b8a

src/Thinktecture.Runtime.Extensions/UnionAttribute.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ public sealed class UnionAttribute : Attribute
2828
/// </summary>
2929
public string? SwitchMapStateParameterName { get; set; }
3030

31-
/// <summary>
32-
/// Indication whether the generator should skip the implementation of <c>IEquatable{T}</c> and any comparison operators.
33-
/// This includes the <c>Equals</c> and <c>GetHashCode</c> methods.
34-
/// </summary>
35-
public bool SkipEqualityComparison { get; set; }
36-
3731
/// <summary>
3832
/// Initializes a new instance of <see cref="UnionAttribute"/>.
3933
/// </summary>

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_skip_equals_method_with_SkipEqualsMethod.verified.txt renamed to test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_skip_equals_method_with_SkipEqualityComparison_using_non_generic_AdHocUnionAttribute.verified.txt

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ namespace Thinktecture.Tests
55
{
66
[global::System.Diagnostics.CodeAnalysis.SuppressMessage("ThinktectureRuntimeExtensionsAnalyzer", "TTRESG1000:Internal Thinktecture.Runtime.Extensions API usage")]
77
sealed partial class TestUnion :
8-
global::System.IEquatable<global::Thinktecture.Tests.TestUnion>,
9-
global::System.Numerics.IEqualityOperators<global::Thinktecture.Tests.TestUnion, global::Thinktecture.Tests.TestUnion, bool>,
108
global::Thinktecture.IDisallowDefaultValue,
119
global::Thinktecture.Internal.IMetadataOwner
1210
{
@@ -456,51 +454,6 @@ namespace Thinktecture.Tests
456454
return obj.AsNullableOfInt32;
457455
}
458456

459-
/// <summary>
460-
/// Compares two instances of <see cref="TestUnion"/>.
461-
/// </summary>
462-
/// <param name="obj">Instance to compare.</param>
463-
/// <param name="other">Another instance to compare.</param>
464-
/// <returns><c>true</c> if objects are equal; otherwise <c>false</c>.</returns>
465-
public static bool operator ==(global::Thinktecture.Tests.TestUnion? obj, global::Thinktecture.Tests.TestUnion? other)
466-
{
467-
if (obj is null)
468-
return other is null;
469-
470-
return obj.Equals(other);
471-
}
472-
473-
/// <summary>
474-
/// Compares two instances of <see cref="TestUnion"/>.
475-
/// </summary>
476-
/// <param name="obj">Instance to compare.</param>
477-
/// <param name="other">Another instance to compare.</param>
478-
/// <returns><c>false</c> if objects are equal; otherwise <c>true</c>.</returns>
479-
public static bool operator !=(global::Thinktecture.Tests.TestUnion? obj, global::Thinktecture.Tests.TestUnion? other)
480-
{
481-
return !(obj == other);
482-
}
483-
484-
/// <inheritdoc />
485-
public override bool Equals(object? other)
486-
{
487-
return other is global::Thinktecture.Tests.TestUnion obj && Equals(obj);
488-
}
489-
490-
/// <inheritdoc />
491-
public override int GetHashCode()
492-
{
493-
return this._valueIndex switch
494-
{
495-
1 => global::System.HashCode.Combine(global::Thinktecture.Tests.TestUnion._typeHashCode, this._int32.GetHashCode()),
496-
2 => global::System.HashCode.Combine(global::Thinktecture.Tests.TestUnion._typeHashCode, ((string?)this._obj)?.GetHashCode(global::System.StringComparison.OrdinalIgnoreCase) ?? 0),
497-
3 => global::System.HashCode.Combine(global::Thinktecture.Tests.TestUnion._typeHashCode, ((global::System.Collections.Generic.List<int>?)this._obj)?.GetHashCode() ?? 0),
498-
4 => global::System.HashCode.Combine(global::Thinktecture.Tests.TestUnion._typeHashCode, this._boolean.GetHashCode()),
499-
5 => global::System.HashCode.Combine(global::Thinktecture.Tests.TestUnion._typeHashCode, this._nullableOfInt32.GetHashCode()),
500-
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
501-
};
502-
}
503-
504457
/// <inheritdoc />
505458
public override string? ToString()
506459
{

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,4 +551,22 @@ public partial class TestUnion;
551551

552552
await VerifyAsync(outputs, "Thinktecture.Tests.TestUnion.AdHocUnion.g.cs");
553553
}
554+
555+
[Fact]
556+
public async Task Should_skip_equals_method_with_SkipEqualityComparison_using_non_generic_AdHocUnionAttribute()
557+
{
558+
var source = """
559+
using System;
560+
using System.Collections.Generic;
561+
562+
namespace Thinktecture.Tests
563+
{
564+
[AdHocUnion(typeof(int), typeof(string), typeof(List<int>), typeof(bool), typeof(int?), SkipEqualityComparison = true)]
565+
public partial class TestUnion;
566+
}
567+
""";
568+
var outputs = GetGeneratedOutputs<AdHocUnionSourceGenerator>(source, typeof(UnionAttribute<,>).Assembly);
569+
570+
await VerifyAsync(outputs, "Thinktecture.Tests.TestUnion.AdHocUnion.g.cs");
571+
}
554572
}

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.Should_not_generate_equals_method_if_SkipEqualsMethod_is_true.verified.txt

Lines changed: 0 additions & 160 deletions
This file was deleted.

0 commit comments

Comments
 (0)