Skip to content

Commit 2aeb10d

Browse files
committed
Union should not generate implicit conversion if the type is an interface
1 parent 442e754 commit 2aeb10d

File tree

5 files changed

+170
-0
lines changed

5 files changed

+170
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public sealed class DefaultMemberState : IMemberState, IEquatable<DefaultMemberS
1313
public bool IsReferenceType => _typedMemberState.IsReferenceType;
1414
public bool IsNullableStruct => _typedMemberState.IsNullableStruct;
1515
public NullableAnnotation NullableAnnotation => _typedMemberState.NullableAnnotation;
16+
public bool IsInterface => _typedMemberState.TypeKind == TypeKind.Interface;
1617

1718
public DefaultMemberState(ITypedMemberState typedMemberState, string name, string argumentName)
1819
{

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Unions/UnionCodeGenerator.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ private void GenerateImplicitConversions()
188188
{
189189
var ctorArg = memberType.UniqueSingleArgumentConstructors[j];
190190

191+
if(ctorArg.IsInterface)
192+
continue;
193+
191194
_sb.Append(@"
192195
193196
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// <auto-generated />
2+
#nullable enable
3+
4+
namespace Thinktecture.Tests;
5+
6+
abstract partial class TestUnion
7+
{
8+
private TestUnion()
9+
{
10+
}
11+
12+
/// <summary>
13+
/// Executes an action depending on the current type.
14+
/// </summary>
15+
/// <param name="child1">The action to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child1"/>.</param>
16+
/// <param name="child2">The action to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child2"/>.</param>
17+
public void Switch(
18+
global::System.Action<global::Thinktecture.Tests.TestUnion.Child1> @child1,
19+
global::System.Action<global::Thinktecture.Tests.TestUnion.Child2> @child2)
20+
{
21+
switch (this)
22+
{
23+
case global::Thinktecture.Tests.TestUnion.Child1 value:
24+
@child1(value);
25+
return;
26+
case global::Thinktecture.Tests.TestUnion.Child2 value:
27+
@child2(value);
28+
return;
29+
default:
30+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
31+
}
32+
}
33+
34+
/// <summary>
35+
/// Executes an action depending on the current type.
36+
/// </summary>
37+
/// <param name="state">State to be passed to the callbacks.</param>
38+
/// <param name="child1">The action to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child1"/>.</param>
39+
/// <param name="child2">The action to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child2"/>.</param>
40+
public void Switch<TState>(
41+
TState state,
42+
global::System.Action<TState, global::Thinktecture.Tests.TestUnion.Child1> @child1,
43+
global::System.Action<TState, global::Thinktecture.Tests.TestUnion.Child2> @child2)
44+
#if NET9_0_OR_GREATER
45+
where TState : allows ref struct
46+
#endif
47+
{
48+
switch (this)
49+
{
50+
case global::Thinktecture.Tests.TestUnion.Child1 value:
51+
@child1(state, value);
52+
return;
53+
case global::Thinktecture.Tests.TestUnion.Child2 value:
54+
@child2(state, value);
55+
return;
56+
default:
57+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
58+
}
59+
}
60+
61+
/// <summary>
62+
/// Executes a function depending on the current type.
63+
/// </summary>
64+
/// <param name="child1">The function to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child1"/>.</param>
65+
/// <param name="child2">The function to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child2"/>.</param>
66+
public TResult Switch<TResult>(
67+
global::System.Func<global::Thinktecture.Tests.TestUnion.Child1, TResult> @child1,
68+
global::System.Func<global::Thinktecture.Tests.TestUnion.Child2, TResult> @child2)
69+
#if NET9_0_OR_GREATER
70+
where TResult : allows ref struct
71+
#endif
72+
{
73+
switch (this)
74+
{
75+
case global::Thinktecture.Tests.TestUnion.Child1 value:
76+
return @child1(value);
77+
case global::Thinktecture.Tests.TestUnion.Child2 value:
78+
return @child2(value);
79+
default:
80+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
81+
}
82+
}
83+
84+
/// <summary>
85+
/// Executes a function depending on the current type.
86+
/// </summary>
87+
/// <param name="state">State to be passed to the callbacks.</param>
88+
/// <param name="child1">The function to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child1"/>.</param>
89+
/// <param name="child2">The function to execute if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child2"/>.</param>
90+
public TResult Switch<TState, TResult>(
91+
TState state,
92+
global::System.Func<TState, global::Thinktecture.Tests.TestUnion.Child1, TResult> @child1,
93+
global::System.Func<TState, global::Thinktecture.Tests.TestUnion.Child2, TResult> @child2)
94+
#if NET9_0_OR_GREATER
95+
where TResult : allows ref struct
96+
where TState : allows ref struct
97+
#endif
98+
{
99+
switch (this)
100+
{
101+
case global::Thinktecture.Tests.TestUnion.Child1 value:
102+
return @child1(state, value);
103+
case global::Thinktecture.Tests.TestUnion.Child2 value:
104+
return @child2(state, value);
105+
default:
106+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
107+
}
108+
}
109+
110+
/// <summary>
111+
/// Maps current instance to an instance of type <typeparamref name="TResult"/>.
112+
/// </summary>
113+
/// <param name="child1">The instance to return if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child1"/>.</param>
114+
/// <param name="child2">The instance to return if the current type is <see cref="global::Thinktecture.Tests.TestUnion.Child2"/>.</param>
115+
public TResult Map<TResult>(
116+
TResult @child1,
117+
TResult @child2)
118+
#if NET9_0_OR_GREATER
119+
where TResult : allows ref struct
120+
#endif
121+
{
122+
switch (this)
123+
{
124+
case global::Thinktecture.Tests.TestUnion.Child1 value:
125+
return @child1;
126+
case global::Thinktecture.Tests.TestUnion.Child2 value:
127+
return @child2;
128+
default:
129+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
130+
}
131+
}
132+
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,31 @@ await VerifyAsync(outputs,
275275
"Thinktecture.Tests.Result`1.g.cs");
276276
}
277277

278+
[Fact]
279+
public async Task Should_not_generate_implicit_conversion_if_derived_has_an_interface()
280+
{
281+
var source = """
282+
using System;
283+
using System.Collections.Generic;
284+
using Thinktecture;
285+
286+
namespace Thinktecture.Tests
287+
{
288+
[Union]
289+
public partial class TestUnion
290+
{
291+
public sealed class Child1(IReadOnlyList<string> List) : TestUnion;
292+
293+
public sealed class Child2 : TestUnion;
294+
}
295+
}
296+
""";
297+
var outputs = GetGeneratedOutputs<UnionSourceGenerator>(source, typeof(UnionAttribute).Assembly);
298+
299+
await VerifyAsync(outputs,
300+
"Thinktecture.Tests.TestUnion.g.cs");
301+
}
302+
278303
[Fact]
279304
public async Task Should_generate_record_with_multiple_implicit_conversions()
280305
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Collections.Generic;
2+
3+
namespace Thinktecture.Runtime.Tests.TestUnions;
4+
5+
[Union]
6+
public partial record TestUnionWithInterface
7+
{
8+
public sealed record Child1(IReadOnlyList<string> List) : TestUnionWithInterface;
9+
}

0 commit comments

Comments
 (0)