Skip to content

Commit 33b2116

Browse files
committed
Regular union is not generating (implicit) conversion to a base class
1 parent 405bb5b commit 33b2116

12 files changed

+813
-6
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>9.6.0</VersionPrefix>
5+
<VersionPrefix>9.6.1</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private static IReadOnlyList<IReadOnlyList<DefaultMemberState>> GetSingleArgumen
191191
(states ??= []).Add(parameterState);
192192
}
193193

194-
statesPerTypeInfo.Add(states ?? (IReadOnlyList<DefaultMemberState>) []);
194+
statesPerTypeInfo.Add(states ?? (IReadOnlyList<DefaultMemberState>)[]);
195195
}
196196

197197
return statesPerTypeInfo;
@@ -246,14 +246,30 @@ private static IReadOnlyList<IParameterSymbol> GetSingleArgumentConstructors(
246246

247247
var parameterCandidate = ctor.Parameters[0];
248248

249-
// Ignore copy constructor
250-
if (SymbolEqualityComparer.Default.Equals(parameterCandidate.Type, derivedTypeInfo.Type))
249+
if (SymbolEqualityComparer.Default.Equals(parameterCandidate.Type, derivedTypeInfo.Type) // Ignore copy constructor
250+
|| IsBaseTypeOf(parameterCandidate.Type, derivedTypeInfo.Type) // Ignore base type constructor
251+
|| IsBaseTypeOf(derivedTypeInfo.Type, parameterCandidate.Type)) // Ignore constructors with derived types
251252
continue;
252253

253254
(parameters ??= []).Add(parameterCandidate);
254255
}
255256

256-
return parameters ?? (IReadOnlyList<IParameterSymbol>) [];
257+
return parameters ?? (IReadOnlyList<IParameterSymbol>)[];
258+
}
259+
260+
private static bool IsBaseTypeOf(ITypeSymbol type, ITypeSymbol potentialBaseType)
261+
{
262+
var baseType = type.BaseType;
263+
264+
while (baseType is not null)
265+
{
266+
if (SymbolEqualityComparer.Default.Equals(baseType, potentialBaseType))
267+
return true;
268+
269+
baseType = baseType.BaseType;
270+
}
271+
272+
return false;
257273
}
258274

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

0 commit comments

Comments
 (0)