-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGeneratorTemplate.cs
More file actions
199 lines (174 loc) · 10.3 KB
/
GeneratorTemplate.cs
File metadata and controls
199 lines (174 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
using CodegenCS;
public class GeneratorTemplate
{
public void Main(ICodegenContext context)
{
string className;
string targetType;
className = "OneOf"; targetType = "Tuple";
GenerateToTupleClass(context[$"OneOf.ToTupleExtensions\\{className}ConvertTo{targetType}Extensions.generated.cs"], className, targetType);
className = "OneOf"; targetType = "ValueTuple";
GenerateToTupleClass(context[$"OneOf.ToTupleExtensions\\{className}ConvertTo{targetType}Extensions.generated.cs"], className, targetType);
className = "OneOfBase"; targetType = "Tuple";
GenerateToTupleClass(context[$"OneOf.ToTupleExtensions\\{className}ConvertTo{targetType}Extensions.generated.cs"], className, targetType);
className = "OneOfBase"; targetType = "ValueTuple";
GenerateToTupleClass(context[$"OneOf.ToTupleExtensions\\{className}ConvertTo{targetType}Extensions.generated.cs"], className, targetType);
className = "OneOf";
GenerateDeconstructClass(context[$"OneOf.DeconstructorExtensions\\{className}DeconstructExtensions.generated.cs"], className);
className = "OneOfBase";
GenerateDeconstructClass(context[$"OneOf.DeconstructorExtensions\\{className}DeconstructExtensions.generated.cs"], className);
}
static void GenerateToTupleClass(ICodegenOutputFile file, string className, string targetType)
{
file.WriteLine($$"""
using System;
namespace OneOf
{
#nullable enable
/// <summary>
/// Extensions that converts {{className}} into a {{targetType}} that can be desconstructed.
/// Only one element of the Tuple will have a non-null value.
/// All generic types of {{className}} should either be non-nullable value types or non-nullable reference types
/// </summary>
public static class {{className}}To{{targetType}}Extensions
{
/// <summary>
/// Constraints are not part of the signature, but parameters are.
/// Constraints in parameters are enforced during overload resolution, so we put the constraints in optional disambiguation parameters.
/// </summary>
public class RequireStruct<T> where T : struct { }
/// <summary>
/// Constraints are not part of the signature, but parameters are.
/// Constraints in parameters are enforced during overload resolution, so we put the constraints in optional disambiguation parameters.
/// </summary>
public class RequireClass<T> where T : class { }
{{GenerateToTupleExtensions(className, targetType)}}
}
#nullable disable
}
""");
}
static IEnumerable<FormattableString> GenerateToTupleExtensions(string className, string targetType)
{
// ValueTuple goes from T1 up to T7, so max number of combinations we can accept are Tuples with 7 elements (T0 to T6)
// so maxTypes should be <= 7.
int maxTypes = 7;
for (int elements = 2; elements <= maxTypes; elements++)
{
// We want all combination of types "C" (non-nullable reference type) or "S" (non-nullable value types)
char[] chars = new char[] { 'C', 'S' };
var typesCombinations = GenerateStrings(chars, elements);
foreach (var typesCombination in typesCombinations)
{
IEnumerable<char> types = typesCombination.ToCharArray();
var typesStr = string.Join(", ", types.Select((type, i) => $"T{i}"));
var typesWithNullables = string.Join(", ", types.Select((type, i) => type == 'C' ? $"T{i}?" : $"Nullable<T{i}>"));
var namedTypesWithNullables = string.Join(", ", types.Select((type, i) => type == 'C' ? $"T{i}? T{i}" : $"Nullable<T{i}> T{i}"));
var constraints = string.Join("\n", types.Select((type, i) => type == 'C' ? $"where T{i} : class" : $"where T{i} : struct"));
var tupleValues = string.Join(",\n", types.Select((type, i) => type == 'C' ? $"oneOf.Index == {i} ? (T{i}?)oneOf.AsT{i} : null" : $"oneOf.Index == {i} ? (Nullable<T{i}>)oneOf.AsT{i} : null"));
var dummyArguments = string.Join(", ", types.Select((type, i) => $"Require{(type == 'C' ? "Class" : "Struct")}<T{i}>? dummy{i} = null"));
var resultConstructor = $"{targetType}<{typesWithNullables}>";
// For Tuple we just declare Tuple<T0, T1, etc>, and the deconstruction unfortunately will be named Item1/Item2/etc. (indexes don't match types)
// For ValueTuple we can use named parameters (T0 t0, T1 t1, etc), although the idea is that consumers will deconstruct into variables and won't even use these names
string resultTypeDeclaration = (targetType == "Tuple") ?
$"{targetType}<{typesWithNullables}>" :
$"({namedTypesWithNullables})";
yield return $$"""
/// <summary>
/// Converts the {{className}} into a {{targetType}}{} that can be desconstructed.
/// Only one element of the Tuple will have a non-null value.
/// All generic types of {{className}} should either be non-nullable value types or non-nullable reference types.
/// All optional parameters named "dummy" will be ignored - they are just used for compiler disambiguation (to find the right combination of reference-types and value-types)
/// </summary>
public static {{resultTypeDeclaration}} To{{targetType}}<{{typesStr}}>(this {{className}}<{{typesStr}}> oneOf, {{dummyArguments}})
{{constraints}}
{
return new {{resultConstructor}}(
{{tupleValues}}
);
}
""";
}
}
}
static void GenerateDeconstructClass(ICodegenOutputFile file, string className)
{
file.WriteLine($$"""
using System;
namespace OneOf
{
#nullable enable
/// <summary>
/// Extensions that deconstruct the {{className}}.
/// Only one of the returned elements will have a non-null value.
/// All generic types of {{className}} should either be non-nullable value types or non-nullable reference types
/// </summary>
public static class {{className}}DeconstructExtensions
{
/// <summary>
/// Constraints are not part of the signature, but parameters are.
/// Constraints in parameters are enforced during overload resolution, so we put the constraints in optional disambiguation parameters.
/// </summary>
public class RequireStruct<T> where T : struct { }
/// <summary>
/// Constraints are not part of the signature, but parameters are.
/// Constraints in parameters are enforced during overload resolution, so we put the constraints in optional disambiguation parameters.
/// </summary>
public class RequireClass<T> where T : class { }
{{GenerateDeconstructExtensions(className)}}
}
#nullable disable
}
""");
}
static IEnumerable<FormattableString> GenerateDeconstructExtensions(string className)
{
// We should create deconstructor for all combinations up to 9 elements (OneOf<T0...T8>)
int maxTypes = 9;
for (int elements = 2; elements <= maxTypes; elements++)
{
// We want all combination of types "C" (non-nullable reference type) or "S" (non-nullable value types)
char[] chars = new char[] { 'C', 'S' };
var typesCombinations = GenerateStrings(chars, elements);
foreach (var typesCombination in typesCombinations)
{
IEnumerable<char> types = typesCombination.ToCharArray();
var typesStr = string.Join(", ", types.Select((type, i) => $"T{i}"));
var typesWithNullables = string.Join(", ", types.Select((type, i) => $"T{i}"));
var namedTypesWithNullables = string.Join(", ", types.Select((type, i) => type == 'C' ? $"out T{i}? item{i}" : $"out Nullable<T{i}> item{i}"));
var constraints = string.Join("\n", types.Select((type, i) => type == 'C' ? $"where T{i} : class" : $"where T{i} : struct"));
var setValues = string.Join("\n", types.Select((type, i) => type == 'C' ? $"item{i} = (oneOf.Index == {i} ? (T{i}?)oneOf.AsT{i} : null);" : $"item{i} = (oneOf.Index == {i} ? (Nullable<T{i}>)oneOf.AsT{i} : null);"));
yield return $$"""
/// <summary>
/// Deconstructs the {{className}}.
/// Only one element of the Tuple will have a non-null value.
/// All generic types of {{className}} should either be non-nullable value types or non-nullable reference types.
/// All optional parameters named "dummy" will be ignored - they are just used for compiler disambiguation (to find the right combination of reference-types and value-types)
/// </summary>
public static void Deconstruct<{{typesWithNullables}}>(this {{className}}<{{typesStr}}> oneOf, {{namedTypesWithNullables}})
{{constraints}}
{
{{setValues}}
}
""";
}
}
}
public static IEnumerable<String> GenerateStrings(IEnumerable<char> characters, int length)
{
if (length > 0)
{
foreach (char c in characters)
{
foreach (String suffix in GenerateStrings(characters, length - 1))
{
yield return c + suffix;
}
}
}
else
{
yield return string.Empty;
}
}
}