Skip to content

Commit 03bd518

Browse files
committed
add configuration of param name generation of nested regular unions
1 parent edf2f0b commit 03bd518

48 files changed

Lines changed: 8300 additions & 104 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE-ATTRIBUTES.md

Lines changed: 157 additions & 98 deletions
Large diffs are not rendered by default.

docs

Submodule docs updated from d606697 to 58e558b
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Thinktecture.Unions;
2+
3+
/// <summary>
4+
/// Example demonstrating Simple parameter name generation for nested unions.
5+
/// This generates shorter parameter names (notFound, unauthorized) instead of
6+
/// the default longer names (failureNotFound, failureUnauthorized).
7+
/// </summary>
8+
[Union(NestedUnionParameterNames = NestedUnionParameterNameGeneration.Simple)]
9+
[UnionSwitchMapOverload(StopAt = [typeof(Failure)])]
10+
public partial class ApiResponseWithSimpleParameterNames
11+
{
12+
public sealed class Success : ApiResponseWithSimpleParameterNames;
13+
14+
[Union]
15+
public abstract partial class Failure : ApiResponseWithSimpleParameterNames
16+
{
17+
public sealed class NotFound : Failure;
18+
19+
public sealed class Unauthorized : Failure;
20+
}
21+
}

samples/Basic.Samples/Unions/DiscriminatedUnionsDemos.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,18 @@ private static void DemoForNestedUnionsAndSwitchMapOverloads(ILogger logger)
305305
306306
""");
307307

308+
logger.Information("--- Default Parameter Names ---");
309+
308310
var apiResponse = new ApiResponse.Failure.Unauthorized();
309311

312+
// With default parameter naming, nested types include their parent union name
313+
apiResponse.Switch(
314+
success: success => logger.Information("[Switch] Success"),
315+
failureNotFound: notFound => logger.Information("[Switch] Not Found"),
316+
failureUnauthorized: unauthorized => logger.Information("[Switch] Unauthorized")
317+
);
318+
319+
// Non-exhaustive overload (stopped at Failure level)
310320
apiResponse.Switch(
311321
success: success => logger.Information("[Switch] Success"),
312322
failure: HandleFailure);
@@ -318,5 +328,29 @@ void HandleFailure(ApiResponse.Failure failure)
318328
unauthorized: unauthorized => logger.Information("[Switch] Unauthorized")
319329
);
320330
}
331+
332+
logger.Information("--- Simple Parameter Names ---");
333+
334+
var apiResponseSimple = new ApiResponseWithSimpleParameterNames.Failure.NotFound();
335+
336+
// With simple parameter naming, nested types use only their own name
337+
apiResponseSimple.Switch(
338+
success: success => logger.Information("[Switch] Success"),
339+
notFound: notFound => logger.Information("[Switch] Not Found"), // Simple name
340+
unauthorized: unauthorized => logger.Information("[Switch] Unauthorized") // Simple name
341+
);
342+
343+
// Non-exhaustive overload (stopped at Failure level)
344+
apiResponseSimple.Switch(
345+
success: success => logger.Information("[Switch] Success"),
346+
failure: HandleFailureSimple);
347+
348+
void HandleFailureSimple(ApiResponseWithSimpleParameterNames.Failure failure)
349+
{
350+
failure.Switch(
351+
notFound: notFound => logger.Information("[Switch] Not Found"),
352+
unauthorized: unauthorized => logger.Information("[Switch] Unauthorized")
353+
);
354+
}
321355
}
322356
}

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public static class Properties
114114
public const string SWITCH_MAP_STATE_PARAMETER_NAME = "SwitchMapStateParameterName";
115115
public const string SWITCH_METHODS = "SwitchMethods";
116116
public const string TRY_CREATE_FACTORY_METHOD_NAME = "TryCreateFactoryMethodName";
117+
public const string NESTED_UNION_PARAMETER_NAMES = "NestedUnionParameterNames";
117118
public const string UNSAFE_CONVERSION_TO_KEY_MEMBER_TYPE = "UnsafeConversionToKeyMemberType";
118119
public const string USE_FOR_MODEL_BINDING = "UseForModelBinding";
119120
public const string USE_FOR_SERIALIZATION = "UseForSerialization";
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Thinktecture.CodeAnalysis;
2+
3+
public enum NestedUnionParameterNameGeneration
4+
{
5+
Default = 0,
6+
Simple = 1
7+
}

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/RegularUnions/RegularUnionCodeGenerator.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,9 @@ private TypeMember MakeTypeMember(
8282
TypeMember? baseType,
8383
StringBuilder sb)
8484
{
85-
// Skip all containing types of the union itself + the union's own type
86-
// This ensures parameter names are just the member name (e.g., "success")
87-
// unless the member is nested in another union within this union
88-
var skipLevels = _state.ContainingTypes.Length + 1;
85+
var skipLevels = _state.Settings.NestedUnionParameterNames == NestedUnionParameterNameGeneration.Simple
86+
? typeMember.ContainingTypes.Length
87+
: _state.ContainingTypes.Length + 1;
8988

9089
var argName = typeMember.ContainingTypes
9190
.MakeFullyQualifiedArgumentName(typeMember.Name, skipLevels, sb);

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/RegularUnions/RegularUnionSettings.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public sealed class RegularUnionSettings : IEquatable<RegularUnionSettings>, IHa
77
public ConversionOperatorsGeneration ConversionFromValue { get; }
88
public string SwitchMapStateParameterName { get; }
99
public ImmutableArray<RegularUnionSwitchMapOverload> SwitchMapOverloads { get; }
10+
public NestedUnionParameterNameGeneration NestedUnionParameterNames { get; }
1011

1112
public RegularUnionSettings(
1213
AttributeData attribute,
@@ -17,6 +18,7 @@ public RegularUnionSettings(
1718
ConversionFromValue = attribute.FindConversionFromValue() ?? ConversionOperatorsGeneration.Implicit;
1819
SwitchMapStateParameterName = attribute.FindSwitchMapStateParameterName();
1920
SwitchMapOverloads = switchMapOverloads;
21+
NestedUnionParameterNames = attribute.FindNestedUnionParameterNameGeneration();
2022
}
2123

2224
public override bool Equals(object? obj)
@@ -36,6 +38,7 @@ public bool Equals(RegularUnionSettings? other)
3638
&& MapMethods == other.MapMethods
3739
&& ConversionFromValue == other.ConversionFromValue
3840
&& SwitchMapStateParameterName == other.SwitchMapStateParameterName
41+
&& NestedUnionParameterNames == other.NestedUnionParameterNames
3942
&& SwitchMapOverloads.SequenceEqual(other.SwitchMapOverloads);
4043
}
4144

@@ -47,6 +50,7 @@ public override int GetHashCode()
4750
hashCode = (hashCode * 397) ^ (int)MapMethods;
4851
hashCode = (hashCode * 397) ^ (int)ConversionFromValue;
4952
hashCode = (hashCode * 397) ^ SwitchMapStateParameterName.GetHashCode();
53+
hashCode = (hashCode * 397) ^ (int)NestedUnionParameterNames;
5054
hashCode = (hashCode * 397) ^ SwitchMapOverloads.ComputeHashCode();
5155

5256
return hashCode;

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ public static string FindSwitchMapStateParameterName(this AttributeData attribut
235235
return GetStringParameterValue(attributeData, Constants.Attributes.Properties.SWITCH_MAP_STATE_PARAMETER_NAME) ?? "state";
236236
}
237237

238+
public static NestedUnionParameterNameGeneration FindNestedUnionParameterNameGeneration(
239+
this AttributeData attribute)
240+
{
241+
return GetEnumParameterValue<NestedUnionParameterNameGeneration>(attribute, Constants.Attributes.Properties.NESTED_UNION_PARAMETER_NAMES)
242+
?? NestedUnionParameterNameGeneration.Default;
243+
}
244+
238245
public static UnionConstructorAccessModifier FindUnionConstructorAccessModifier(this AttributeData attributeData)
239246
{
240247
return GetEnumParameterValue<UnionConstructorAccessModifier>(attributeData, Constants.Attributes.Properties.CONSTRUCTOR_ACCESS_MODIFIER)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace Thinktecture;
2+
3+
/// <summary>
4+
/// Controls how parameter names are generated in Switch/Map methods for nested Regular Union types.
5+
/// </summary>
6+
public enum NestedUnionParameterNameGeneration
7+
{
8+
/// <summary>
9+
/// Default naming strategy: Includes intermediate type names in the parameter name.
10+
/// For example, a nested type <c>Failure.NotFound</c> generates the parameter name <c>failureNotFound</c>.
11+
/// This ensures uniqueness when multiple nested unions exist but can result in longer parameter names.
12+
/// </summary>
13+
Default = 0,
14+
15+
/// <summary>
16+
/// Simple naming strategy: Uses only the final type name as the parameter name.
17+
/// For example, a nested type <c>Failure.NotFound</c> generates the parameter name <c>notFound</c>.
18+
/// <para>
19+
/// <b>WARNING:</b> This can cause parameter name collisions if multiple nested unions have types
20+
/// with the same name. The C# compiler will report duplicate parameter errors if conflicts occur.
21+
/// </para>
22+
/// <example>
23+
/// This will cause a compilation error:
24+
/// <code>
25+
/// [Union(NestedUnionParameterNames = NestedUnionParameterNameGeneration.Simple)]
26+
/// public partial class Response
27+
/// {
28+
/// [Union]
29+
/// public abstract partial class Success : Response
30+
/// {
31+
/// public sealed class Status : Success; // Parameter: status
32+
/// }
33+
///
34+
/// [Union]
35+
/// public abstract partial class Failure : Response
36+
/// {
37+
/// public sealed class Status : Failure; // Parameter: status (CONFLICT!)
38+
/// }
39+
/// }
40+
/// </code>
41+
/// </example>
42+
/// </summary>
43+
Simple = 1
44+
}

0 commit comments

Comments
 (0)