Skip to content

Commit edf2f0b

Browse files
committed
don't add the union name to args if union itself is nested
1 parent baeabda commit edf2f0b

9 files changed

Lines changed: 231 additions & 12 deletions

File tree

samples/Basic.Samples/Unions/DiscriminatedUnionsDemos.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ private static void DemoForNestedUnionsAndSwitchMapOverloads(ILogger logger)
314314
void HandleFailure(ApiResponse.Failure failure)
315315
{
316316
failure.Switch(
317-
failureNotFound: notFound => logger.Information("[Switch] Not Found"),
318-
failureUnauthorized: unauthorized => logger.Information("[Switch] Unauthorized")
317+
notFound: notFound => logger.Information("[Switch] Not Found"),
318+
unauthorized: unauthorized => logger.Information("[Switch] Unauthorized")
319319
);
320320
}
321321
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,18 @@ private List<TypeMember> OrderTypeMembers(
7777
return typeMembers;
7878
}
7979

80-
private static TypeMember MakeTypeMember(
80+
private TypeMember MakeTypeMember(
8181
RegularUnionTypeMemberState typeMember,
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;
89+
8590
var argName = typeMember.ContainingTypes
86-
.MakeFullyQualifiedArgumentName(typeMember.Name, skipRootContainingType: true, sb);
91+
.MakeFullyQualifiedArgumentName(typeMember.Name, skipLevels, sb);
8792

8893
return new TypeMember(typeMember, argName, baseType, []);
8994
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ public static class ContainingTypesExtensions
88
public static string MakeFullyQualifiedArgumentName(
99
this ImmutableArray<ContainingTypeState> containingTypes,
1010
string typeMemberName,
11-
bool skipRootContainingType,
11+
int skipLevels,
1212
StringBuilder sb)
1313
{
1414
var originalLen = sb.Length;
1515

16-
for (var i = skipRootContainingType ? 1 : 0; i < containingTypes.Length; i++)
16+
for (var i = skipLevels; i < containingTypes.Length; i++)
17+
{
1718
sb.Append(containingTypes[i].Name);
19+
}
1820

1921
sb.Append(typeMemberName);
2022

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/Internal/ThinktectureRequestBodyFilter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ private void ReplaceExpandedSchema(
5858
if (metadata is null)
5959
{
6060
var children = node.Switch(
61-
nodeNamedLeaf: _ => [],
62-
nodeNamedContainer: n => n.Children,
63-
nodeRoot: n => n.Children);
61+
namedLeaf: _ => [],
62+
namedContainer: n => n.Children,
63+
root: n => n.Children);
6464

6565
foreach (var child in children)
6666
{

src/Thinktecture.Runtime.Extensions/Internal/MetadataLookup.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ private static bool IsCandidate([NotNullWhen(true)] Type? type)
109109
private static (LambdaExpression? FromCtor, LambdaExpression? FromFactory) GetFromExpressions(Metadata.Keyed keyedMetadata)
110110
{
111111
return keyedMetadata.Switch(
112-
keyedSmartEnum: m => (null, m.ConvertFromKeyExpression),
113-
keyedValueObject: m => ((LambdaExpression?)m.ConvertFromKeyExpressionViaConstructor, m.ConvertFromKeyExpression));
112+
smartEnum: m => (null, m.ConvertFromKeyExpression),
113+
valueObject: m => ((LambdaExpression?)m.ConvertFromKeyExpressionViaConstructor, m.ConvertFromKeyExpression));
114114
}
115115

116116
private static ConversionMetadata? GetConversionMetadata(

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/Extensions/ContainingTypesExtensionsTests/MakeFullyQualifiedArgumentName.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void MakeFullyQualifiedArgumentName_Works(string[] types, string member,
2626
{
2727
var list = types.Select(n => new ContainingTypeState(n, true, false, [])).ToImmutableArray();
2828
var sb = new StringBuilder("prefix"); // ensure restoration works
29-
var result = ContainingTypesExtensions.MakeFullyQualifiedArgumentName(list, member, skipRoot, sb);
29+
var result = ContainingTypesExtensions.MakeFullyQualifiedArgumentName(list, member, skipLevels: skipRoot ? 1 : 0, sb);
3030

3131
result.Should().Be(expected);
3232
sb.Length.Should().Be("prefix".Length); // sb is restored
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// <auto-generated />
2+
#nullable enable
3+
4+
namespace Thinktecture.Tests;
5+
6+
partial class Container
7+
{
8+
[global::System.Diagnostics.CodeAnalysis.SuppressMessage("ThinktectureRuntimeExtensionsAnalyzer", "TTRESG1000:Internal Thinktecture.Runtime.Extensions API usage")]
9+
abstract partial record Result :
10+
global::Thinktecture.Internal.IMetadataOwner
11+
{
12+
static global::Thinktecture.Internal.Metadata global::Thinktecture.Internal.IMetadataOwner.Metadata { get; }
13+
= new global::Thinktecture.Internal.Metadata.RegularUnion(typeof(global::Thinktecture.Tests.Container.Result))
14+
{
15+
TypeMembers = new global::System.Collections.Generic.List<global::System.Type>
16+
{
17+
typeof(global::Thinktecture.Tests.Container.Result.Success),
18+
typeof(global::Thinktecture.Tests.Container.Result.Failure)
19+
}
20+
.AsReadOnly()
21+
};
22+
23+
private Result()
24+
{
25+
}
26+
27+
#pragma warning disable CS0436 // InstantHandleAttribute may come from a different assembly
28+
/// <summary>
29+
/// Executes an action depending on the current type.
30+
/// </summary>
31+
/// <param name="success">The action to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Success"/>.</param>
32+
/// <param name="failure">The action to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Failure"/>.</param>
33+
[global::System.Diagnostics.DebuggerStepThroughAttribute]
34+
public void Switch(
35+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Action<global::Thinktecture.Tests.Container.Result.Success> @success,
36+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Action<global::Thinktecture.Tests.Container.Result.Failure> @failure)
37+
{
38+
switch (this)
39+
{
40+
case global::Thinktecture.Tests.Container.Result.Success value:
41+
@success(value);
42+
return;
43+
case global::Thinktecture.Tests.Container.Result.Failure value:
44+
@failure(value);
45+
return;
46+
default:
47+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
48+
}
49+
}
50+
#pragma warning restore CS0436 // InstantHandleAttribute may come from a different assembly
51+
52+
#pragma warning disable CS0436 // InstantHandleAttribute may come from a different assembly
53+
/// <summary>
54+
/// Executes an action depending on the current type.
55+
/// </summary>
56+
/// <param name="state">State to be passed to the callbacks.</param>
57+
/// <param name="success">The action to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Success"/>.</param>
58+
/// <param name="failure">The action to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Failure"/>.</param>
59+
[global::System.Diagnostics.DebuggerStepThroughAttribute]
60+
public void Switch<TState>(
61+
TState @state,
62+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Action<TState, global::Thinktecture.Tests.Container.Result.Success> @success,
63+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Action<TState, global::Thinktecture.Tests.Container.Result.Failure> @failure)
64+
#if NET9_0_OR_GREATER
65+
where TState : allows ref struct
66+
#endif
67+
{
68+
switch (this)
69+
{
70+
case global::Thinktecture.Tests.Container.Result.Success value:
71+
@success(@state, value);
72+
return;
73+
case global::Thinktecture.Tests.Container.Result.Failure value:
74+
@failure(@state, value);
75+
return;
76+
default:
77+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
78+
}
79+
}
80+
#pragma warning restore CS0436 // InstantHandleAttribute may come from a different assembly
81+
82+
#pragma warning disable CS0436 // InstantHandleAttribute may come from a different assembly
83+
/// <summary>
84+
/// Executes a function depending on the current type.
85+
/// </summary>
86+
/// <param name="success">The function to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Success"/>.</param>
87+
/// <param name="failure">The function to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Failure"/>.</param>
88+
[global::System.Diagnostics.DebuggerStepThroughAttribute]
89+
public TResult Switch<TResult>(
90+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Func<global::Thinktecture.Tests.Container.Result.Success, TResult> @success,
91+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Func<global::Thinktecture.Tests.Container.Result.Failure, TResult> @failure)
92+
#if NET9_0_OR_GREATER
93+
where TResult : allows ref struct
94+
#endif
95+
{
96+
switch (this)
97+
{
98+
case global::Thinktecture.Tests.Container.Result.Success value:
99+
return @success(value);
100+
case global::Thinktecture.Tests.Container.Result.Failure value:
101+
return @failure(value);
102+
default:
103+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
104+
}
105+
}
106+
#pragma warning restore CS0436 // InstantHandleAttribute may come from a different assembly
107+
108+
#pragma warning disable CS0436 // InstantHandleAttribute may come from a different assembly
109+
/// <summary>
110+
/// Executes a function depending on the current type.
111+
/// </summary>
112+
/// <param name="state">State to be passed to the callbacks.</param>
113+
/// <param name="success">The function to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Success"/>.</param>
114+
/// <param name="failure">The function to execute if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Failure"/>.</param>
115+
[global::System.Diagnostics.DebuggerStepThroughAttribute]
116+
public TResult Switch<TState, TResult>(
117+
TState @state,
118+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Func<TState, global::Thinktecture.Tests.Container.Result.Success, TResult> @success,
119+
[global::JetBrains.Annotations.InstantHandleAttribute] global::System.Func<TState, global::Thinktecture.Tests.Container.Result.Failure, TResult> @failure)
120+
#if NET9_0_OR_GREATER
121+
where TResult : allows ref struct
122+
where TState : allows ref struct
123+
#endif
124+
{
125+
switch (this)
126+
{
127+
case global::Thinktecture.Tests.Container.Result.Success value:
128+
return @success(@state, value);
129+
case global::Thinktecture.Tests.Container.Result.Failure value:
130+
return @failure(@state, value);
131+
default:
132+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
133+
}
134+
}
135+
#pragma warning restore CS0436 // InstantHandleAttribute may come from a different assembly
136+
137+
#pragma warning disable CS0108 // Map in nested union hides Map from base class
138+
/// <summary>
139+
/// Maps current instance to an instance of type <typeparamref name="TResult"/>.
140+
/// </summary>
141+
/// <param name="success">The instance to return if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Success"/>.</param>
142+
/// <param name="failure">The instance to return if the current type is <see cref="global::Thinktecture.Tests.Container.Result.Failure"/>.</param>
143+
[global::System.Diagnostics.DebuggerStepThroughAttribute]
144+
public TResult Map<TResult>(
145+
TResult @success,
146+
TResult @failure)
147+
#if NET9_0_OR_GREATER
148+
where TResult : allows ref struct
149+
#endif
150+
{
151+
switch (this)
152+
{
153+
case global::Thinktecture.Tests.Container.Result.Success value:
154+
return @success;
155+
case global::Thinktecture.Tests.Container.Result.Failure value:
156+
return @failure;
157+
default:
158+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
159+
}
160+
}
161+
#pragma warning restore CS0108 // Map in nested union hides Map from base class
162+
163+
/// <summary>
164+
/// Implicit conversion from type <see cref="string"/>.
165+
/// </summary>
166+
/// <param name="error">Value to convert from.</param>
167+
/// <returns>A new instance of <see cref="global::Thinktecture.Tests.Container.Result.Failure"/> converted from <paramref name="error"/>.</returns>
168+
public static implicit operator global::Thinktecture.Tests.Container.Result(string @error)
169+
{
170+
return new global::Thinktecture.Tests.Container.Result.Failure(@error);
171+
}
172+
}
173+
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,4 +1432,30 @@ public sealed class L5(string Value) : L4;
14321432
await VerifyAsync(outputs,
14331433
"Thinktecture.Tests.TestUnion.RegularUnion.g.cs");
14341434
}
1435+
1436+
[Fact]
1437+
public async Task Should_not_prefix_parameter_names_when_union_is_inside_regular_class()
1438+
{
1439+
var source = """
1440+
using System;
1441+
using Thinktecture;
1442+
1443+
namespace Thinktecture.Tests
1444+
{
1445+
public partial class Container
1446+
{
1447+
[Union]
1448+
public partial record Result
1449+
{
1450+
public sealed record Success : Result;
1451+
public sealed record Failure(string Error) : Result;
1452+
}
1453+
}
1454+
}
1455+
""";
1456+
var outputs = GetGeneratedOutputs<RegularUnionSourceGenerator>(source, typeof(UnionAttribute).Assembly);
1457+
1458+
await VerifyAsync(outputs,
1459+
"Thinktecture.Tests.Container.Result.RegularUnion.g.cs");
1460+
}
14351461
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Thinktecture.Runtime.Tests.TestRegularUnions;
2+
3+
public partial class ResultInsideClass
4+
{
5+
[Union(SwitchMethods = SwitchMapMethodsGeneration.DefaultWithPartialOverloads,
6+
MapMethods = SwitchMapMethodsGeneration.DefaultWithPartialOverloads)]
7+
public partial record Result
8+
{
9+
public sealed record Success : Result;
10+
11+
public sealed record Failure(string Error) : Result;
12+
}
13+
}

0 commit comments

Comments
 (0)