Skip to content

Commit 5867af1

Browse files
committed
Implicit conversion is not generated for regular unions if it contains required members.
1 parent c9d1370 commit 5867af1

10 files changed

Lines changed: 697 additions & 9 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Thinktecture.CodeAnalysis;
2+
3+
public readonly record struct DerivedTypeInfo(
4+
INamedTypeSymbol Type,
5+
INamedTypeSymbol TypeDef,
6+
int Level);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ private void GenerateImplicitConversions()
181181
{
182182
var memberType = _state.TypeMembers[i];
183183

184-
if (memberType.IsInterface || memberType.IsAbstract)
184+
if (memberType.IsInterface || memberType.IsAbstract || memberType.HasRequiredMembers)
185185
continue;
186186

187187
for (var j = 0; j < memberType.UniqueSingleArgumentConstructors.Count; j++)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ private bool IsCandidate(SyntaxNode syntaxNode, CancellationToken cancellationTo
148148

149149
private static IReadOnlyList<IReadOnlyList<DefaultMemberState>> GetSingleArgumentConstructors(
150150
TypedMemberStateFactory factory,
151-
IReadOnlyList<(INamedTypeSymbol Type, INamedTypeSymbol TypeDef, int Level)> derivedTypeInfos)
151+
IReadOnlyList<DerivedTypeInfo> derivedTypeInfos)
152152
{
153153
var (typeInfoCtors, foundArgTypes) = GetTypeInfoCtors(derivedTypeInfos);
154154

@@ -181,7 +181,7 @@ private static IReadOnlyList<IReadOnlyList<DefaultMemberState>> GetSingleArgumen
181181
}
182182

183183
private static (IReadOnlyList<IReadOnlyList<IParameterSymbol>> TypeInfoCtors, IReadOnlyList<(ITypeSymbol Type, int Counter)> ArgTypes) GetTypeInfoCtors(
184-
IReadOnlyList<(INamedTypeSymbol Type, INamedTypeSymbol TypeDef, int Level)> derivedTypeInfos)
184+
IReadOnlyList<DerivedTypeInfo> derivedTypeInfos)
185185
{
186186
var typeInfoCtors = new List<IReadOnlyList<IParameterSymbol>>(derivedTypeInfos.Count);
187187
var argTypes = new List<(ITypeSymbol Type, int Counter)>();
@@ -212,7 +212,7 @@ private static (IReadOnlyList<IReadOnlyList<IParameterSymbol>> TypeInfoCtors, IR
212212
}
213213

214214
private static IReadOnlyList<IParameterSymbol> GetSingleArgumentConstructors(
215-
(INamedTypeSymbol Type, INamedTypeSymbol TypeDef, int Level) derivedTypeInfo)
215+
DerivedTypeInfo derivedTypeInfo)
216216
{
217217
if (derivedTypeInfo.Type.Constructors.IsDefaultOrEmpty)
218218
return [];

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public class UnionTypeMemberState : IEquatable<UnionTypeMemberState>, ITypeFully
77
public string Name { get; }
88
public bool IsAbstract { get; }
99
public bool IsInterface { get; }
10+
public bool HasRequiredMembers { get; }
1011
public string BaseTypeFullyQualified { get; }
1112
public string BaseTypeDefinitionFullyQualified { get; }
1213
public IReadOnlyList<DefaultMemberState> UniqueSingleArgumentConstructors { get; }
@@ -28,6 +29,7 @@ public UnionTypeMemberState(
2829
IsAbstract = type.IsAbstract;
2930
IsInterface = type.TypeKind == TypeKind.Interface;
3031
UniqueSingleArgumentConstructors = uniqueSingleArgumentConstructors;
32+
HasRequiredMembers = type.HasRequiredMembers();
3133

3234
ContainingTypes = type.GetContainingTypes();
3335
}
@@ -43,6 +45,7 @@ public bool Equals(UnionTypeMemberState? other)
4345
&& BaseTypeFullyQualified == other.BaseTypeFullyQualified
4446
&& IsAbstract == other.IsAbstract
4547
&& IsInterface == other.IsInterface
48+
&& HasRequiredMembers == other.HasRequiredMembers
4649
&& UniqueSingleArgumentConstructors.SequenceEqual(other.UniqueSingleArgumentConstructors)
4750
&& ContainingTypes.SequenceEqual(other.ContainingTypes);
4851
}
@@ -55,6 +58,7 @@ public override int GetHashCode()
5558
hashCode = (hashCode * 397) ^ BaseTypeFullyQualified.GetHashCode();
5659
hashCode = (hashCode * 397) ^ IsAbstract.GetHashCode();
5760
hashCode = (hashCode * 397) ^ IsInterface.GetHashCode();
61+
hashCode = (hashCode * 397) ^ HasRequiredMembers.GetHashCode();
5862
hashCode = (hashCode * 397) ^ UniqueSingleArgumentConstructors.ComputeHashCode();
5963
hashCode = (hashCode * 397) ^ ContainingTypes.ComputeHashCode();
6064

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,25 +387,25 @@ public static INamedTypeSymbol GetGenericTypeDefinition(
387387
: type;
388388
}
389389

390-
public static IReadOnlyList<(INamedTypeSymbol Type, INamedTypeSymbol TypeDef, int Level)> FindDerivedInnerTypes(
390+
public static IReadOnlyList<DerivedTypeInfo> FindDerivedInnerTypes(
391391
this INamedTypeSymbol baseType)
392392
{
393-
List<(INamedTypeSymbol, INamedTypeSymbol, int Level)>? derivedTypes = null;
393+
List<DerivedTypeInfo>? derivedTypes = null;
394394

395395
FindDerivedInnerTypes(
396396
baseType,
397397
0,
398398
(baseType, baseType.GetGenericTypeDefinition()),
399399
ref derivedTypes);
400400

401-
return derivedTypes ?? (IReadOnlyList<(INamedTypeSymbol, INamedTypeSymbol, int)>)Array.Empty<(INamedTypeSymbol, INamedTypeSymbol, int)>();
401+
return derivedTypes ?? (IReadOnlyList<DerivedTypeInfo>)Array.Empty<DerivedTypeInfo>();
402402
}
403403

404404
private static void FindDerivedInnerTypes(
405405
INamedTypeSymbol typeToCheck,
406406
int currentLevel,
407407
(INamedTypeSymbol Type, INamedTypeSymbol TypeDef) baseType,
408-
ref List<(INamedTypeSymbol Type, INamedTypeSymbol TypeDef, int Level)>? derivedTypes)
408+
ref List<DerivedTypeInfo>? derivedTypes)
409409
{
410410
currentLevel++;
411411

@@ -424,7 +424,7 @@ private static void FindDerivedInnerTypes(
424424

425425
if (IsDerivedFrom(innerType, baseType))
426426
{
427-
(derivedTypes ??= []).Add((innerType, innerType.GetGenericTypeDefinition(), currentLevel));
427+
(derivedTypes ??= []).Add(new(innerType, innerType.GetGenericTypeDefinition(), currentLevel));
428428
}
429429

430430
FindDerivedInnerTypes(innerType, currentLevel, baseType, ref derivedTypes);
@@ -735,4 +735,28 @@ public static IReadOnlyList<DelegateMethodState> GetDelegateMethods(
735735

736736
return methodStates ?? (IReadOnlyList<DelegateMethodState>) [];
737737
}
738+
739+
public static bool HasRequiredMembers(this INamedTypeSymbol type)
740+
{
741+
var typeToCheck = type;
742+
743+
while (typeToCheck is not null)
744+
{
745+
var members = typeToCheck.GetMembers();
746+
747+
for (var i = 0; i < members.Length; i++)
748+
{
749+
switch (members[i])
750+
{
751+
case IPropertySymbol { IsRequired: true }:
752+
case IFieldSymbol { IsRequired: true }:
753+
return true;
754+
}
755+
}
756+
757+
typeToCheck = typeToCheck.BaseType;
758+
}
759+
760+
return false;
761+
}
738762
}
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 record Result<T>
7+
{
8+
private Result()
9+
{
10+
}
11+
12+
/// <summary>
13+
/// Executes an action depending on the current type.
14+
/// </summary>
15+
/// <param name="success">The action to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Success</c>.</param>
16+
/// <param name="failure">The action to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Failure</c>.</param>
17+
public void Switch(
18+
global::System.Action<global::Thinktecture.Tests.Result<T>.Success> @success,
19+
global::System.Action<global::Thinktecture.Tests.Result<T>.Failure> @failure)
20+
{
21+
switch (this)
22+
{
23+
case global::Thinktecture.Tests.Result<T>.Success value:
24+
@success(value);
25+
return;
26+
case global::Thinktecture.Tests.Result<T>.Failure value:
27+
@failure(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="success">The action to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Success</c>.</param>
39+
/// <param name="failure">The action to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Failure</c>.</param>
40+
public void Switch<TState>(
41+
TState state,
42+
global::System.Action<TState, global::Thinktecture.Tests.Result<T>.Success> @success,
43+
global::System.Action<TState, global::Thinktecture.Tests.Result<T>.Failure> @failure)
44+
#if NET9_0_OR_GREATER
45+
where TState : allows ref struct
46+
#endif
47+
{
48+
switch (this)
49+
{
50+
case global::Thinktecture.Tests.Result<T>.Success value:
51+
@success(state, value);
52+
return;
53+
case global::Thinktecture.Tests.Result<T>.Failure value:
54+
@failure(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="success">The function to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Success</c>.</param>
65+
/// <param name="failure">The function to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Failure</c>.</param>
66+
public TResult Switch<TResult>(
67+
global::System.Func<global::Thinktecture.Tests.Result<T>.Success, TResult> @success,
68+
global::System.Func<global::Thinktecture.Tests.Result<T>.Failure, TResult> @failure)
69+
#if NET9_0_OR_GREATER
70+
where TResult : allows ref struct
71+
#endif
72+
{
73+
switch (this)
74+
{
75+
case global::Thinktecture.Tests.Result<T>.Success value:
76+
return @success(value);
77+
case global::Thinktecture.Tests.Result<T>.Failure value:
78+
return @failure(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="success">The function to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Success</c>.</param>
89+
/// <param name="failure">The function to execute if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Failure</c>.</param>
90+
public TResult Switch<TState, TResult>(
91+
TState state,
92+
global::System.Func<TState, global::Thinktecture.Tests.Result<T>.Success, TResult> @success,
93+
global::System.Func<TState, global::Thinktecture.Tests.Result<T>.Failure, TResult> @failure)
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.Result<T>.Success value:
102+
return @success(state, value);
103+
case global::Thinktecture.Tests.Result<T>.Failure value:
104+
return @failure(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="success">The instance to return if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Success</c>.</param>
114+
/// <param name="failure">The instance to return if the current type is <c>global::Thinktecture.Tests.Result&lt;T&gt;.Failure</c>.</param>
115+
public TResult Map<TResult>(
116+
TResult @success,
117+
TResult @failure)
118+
#if NET9_0_OR_GREATER
119+
where TResult : allows ref struct
120+
#endif
121+
{
122+
switch (this)
123+
{
124+
case global::Thinktecture.Tests.Result<T>.Success value:
125+
return @success;
126+
case global::Thinktecture.Tests.Result<T>.Failure value:
127+
return @failure;
128+
default:
129+
throw new global::System.ArgumentOutOfRangeException($"Unexpected type '{this.GetType().FullName}'.");
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)