Skip to content

Commit b2658b8

Browse files
committed
String-based smart enums are ISpanParsable
1 parent d3133b5 commit b2658b8

File tree

14 files changed

+1979
-431
lines changed

14 files changed

+1979
-431
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public bool Equals(ICodeGeneratorFactory<T> other)
2929

3030
public static class InterfaceCodeGeneratorFactory
3131
{
32-
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsable = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.Default);
32+
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsableForValueObject = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.ForValueObject);
33+
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsableForEnum = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.ForEnum);
3334
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsableForValidatableEnum = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.ForValidatableEnum);
3435
private static readonly ICodeGeneratorFactory<InterfaceCodeGeneratorState> _comparable = new InterfaceCodeGeneratorFactory<InterfaceCodeGeneratorState>(ComparableCodeGenerator.Default);
3536

@@ -40,9 +41,13 @@ public static ICodeGeneratorFactory<InterfaceCodeGeneratorState> Comparable(stri
4041
return String.IsNullOrWhiteSpace(comparerAccessor) ? _comparable : new InterfaceCodeGeneratorFactory<InterfaceCodeGeneratorState>(new ComparableCodeGenerator(comparerAccessor));
4142
}
4243

43-
public static ICodeGeneratorFactory<ParsableGeneratorState> Parsable(bool forValidatableEnum)
44+
public static ICodeGeneratorFactory<ParsableGeneratorState> Parsable(
45+
bool forEnum,
46+
bool forValidatableEnum)
4447
{
45-
return forValidatableEnum ? _parsableForValidatableEnum : _parsable;
48+
return forEnum
49+
? forValidatableEnum ? _parsableForValidatableEnum : _parsableForEnum
50+
: _parsableForValueObject;
4651
}
4752

4853
public static bool EqualityComparison(

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

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,79 @@ namespace Thinktecture.CodeAnalysis;
44

55
public sealed class ParsableCodeGenerator : IInterfaceCodeGenerator<ParsableGeneratorState>
66
{
7-
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> Default = new ParsableCodeGenerator(false);
8-
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForValidatableEnum = new ParsableCodeGenerator(true);
7+
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForValueObject = new ParsableCodeGenerator(false, false);
8+
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForEnum = new ParsableCodeGenerator(true, false);
9+
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForValidatableEnum = new ParsableCodeGenerator(true, true);
910

11+
private readonly bool _isForEnum;
1012
private readonly bool _isForValidatableEnum;
1113

1214
public string CodeGeneratorName => "Parsable-CodeGenerator";
1315
public string FileNameSuffix => ".Parsable";
1416

15-
private ParsableCodeGenerator(bool isForValidatableEnum)
17+
private ParsableCodeGenerator(
18+
bool isForEnum,
19+
bool isForValidatableEnum)
1620
{
21+
_isForEnum = isForEnum;
1722
_isForValidatableEnum = isForValidatableEnum;
1823
}
1924

2025
public void GenerateBaseTypes(StringBuilder sb, ParsableGeneratorState state)
2126
{
2227
sb.Append(@"
2328
global::System.IParsable<").AppendTypeFullyQualified(state.Type).Append(">");
29+
30+
if (_isForEnum && state.KeyMember?.IsString() == true)
31+
{
32+
sb.Append(@"
33+
#if NET9_0_OR_GREATER
34+
, global::System.ISpanParsable<").AppendTypeFullyQualified(state.Type).Append(@">
35+
#endif");
36+
}
2437
}
2538

2639
public void GenerateImplementation(StringBuilder sb, ParsableGeneratorState state)
2740
{
41+
var isKeyTypeString = state.KeyMember?.IsString() == true;
42+
2843
GenerateValidate(sb, state);
44+
2945
GenerateParse(sb, state);
46+
47+
if (_isForEnum && isKeyTypeString)
48+
GenerateParseForReadOnlySpanOfChar(sb, state);
49+
3050
GenerateTryParse(sb, state);
51+
52+
if (_isForEnum && isKeyTypeString)
53+
GenerateTryParseForReadOnlySpanOfChar(sb, state);
3154
}
3255

33-
private static void GenerateValidate(StringBuilder sb, ParsableGeneratorState state)
56+
private void GenerateValidate(StringBuilder sb, ParsableGeneratorState state)
3457
{
35-
var keyType = state.KeyMember?.IsString() == true || state.HasStringBasedValidateMethod ? "string" : state.KeyMember?.TypeFullyQualified;
58+
var isKeyTypeString = state.KeyMember?.IsString() == true;
59+
var keyType = isKeyTypeString || state.HasStringBasedValidateMethod ? "string" : state.KeyMember?.TypeFullyQualified;
60+
3661
sb.Append(@"
3762
private static ").AppendTypeFullyQualified(state.ValidationError).Append("? Validate<T>(").Append(keyType).Append(" key, global::System.IFormatProvider? provider, out ").AppendTypeFullyQualifiedNullAnnotated(state.Type).Append(@" result)
3863
where T : global::Thinktecture.IValueObjectFactory<").AppendTypeFullyQualified(state.Type).Append(", ").Append(keyType).Append(", ").AppendTypeFullyQualified(state.ValidationError).Append(@">
3964
{
4065
return T.Validate(key, provider, out result);
4166
}");
67+
68+
if (_isForEnum && isKeyTypeString)
69+
{
70+
sb.Append(@"
71+
72+
#if NET9_0_OR_GREATER
73+
private static ").AppendTypeFullyQualified(state.ValidationError).Append("? Validate<T>(global::System.ReadOnlySpan<char> key, global::System.IFormatProvider? provider, out ").AppendTypeFullyQualifiedNullAnnotated(state.Type).Append(@" result)
74+
where T : global::Thinktecture.IValueObjectFactory<").AppendTypeFullyQualified(state.Type).Append(", global::System.ReadOnlySpan<char>, ").AppendTypeFullyQualified(state.ValidationError).Append(@">
75+
{
76+
return T.Validate(key, provider, out result);
77+
}
78+
#endif");
79+
}
4280
}
4381

4482
private void GenerateParse(StringBuilder sb, ParsableGeneratorState state)
@@ -79,6 +117,39 @@ private void GenerateParse(StringBuilder sb, ParsableGeneratorState state)
79117
}
80118
}
81119

120+
private void GenerateParseForReadOnlySpanOfChar(StringBuilder sb, ParsableGeneratorState state)
121+
{
122+
sb.Append(@"
123+
124+
#if NET9_0_OR_GREATER
125+
/// <inheritdoc />
126+
public static ").AppendTypeFullyQualified(state.Type).Append(@" Parse(global::System.ReadOnlySpan<char> s, global::System.IFormatProvider? provider)
127+
{");
128+
129+
sb.Append(@"
130+
var validationError = Validate<").AppendTypeFullyQualified(state.Type).Append(">(s, provider, out var result);");
131+
132+
if (_isForValidatableEnum)
133+
{
134+
sb.Append(@"
135+
return result!;
136+
}");
137+
}
138+
else
139+
{
140+
sb.Append(@"
141+
142+
if(validationError is null)
143+
return result!;
144+
145+
throw new global::System.FormatException(validationError.ToString() ?? ""Unable to parse \""").Append(state.Type.Name).Append(@"\""."");
146+
}");
147+
}
148+
149+
sb.Append(@"
150+
#endif");
151+
}
152+
82153
private void GenerateTryParse(StringBuilder sb, ParsableGeneratorState state)
83154
{
84155
sb.Append(@"
@@ -127,4 +198,34 @@ public static bool TryParse(
127198
}");
128199
}
129200
}
201+
202+
private void GenerateTryParseForReadOnlySpanOfChar(StringBuilder sb, ParsableGeneratorState state)
203+
{
204+
sb.Append(@"
205+
206+
#if NET9_0_OR_GREATER
207+
/// <inheritdoc />
208+
public static bool TryParse(
209+
global::System.ReadOnlySpan<char> s,
210+
global::System.IFormatProvider? provider,
211+
[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out ").AppendTypeFullyQualified(state.Type).Append(@" result)
212+
{
213+
var validationError = Validate<").AppendTypeFullyQualified(state.Type).Append(">(s, provider, out result!);");
214+
215+
if (_isForValidatableEnum)
216+
{
217+
sb.Append(@"
218+
return true;
219+
}");
220+
}
221+
else
222+
{
223+
sb.Append(@"
224+
return validationError is null;
225+
}");
226+
}
227+
228+
sb.Append(@"
229+
#endif");
230+
}
130231
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Thinktecture.CodeAnalysis;
77
public ValidationErrorState ValidationError { get; }
88
public bool SkipIParsable { get; }
99
public bool IsKeyMemberParsable { get; }
10+
public bool IsEnum { get; }
1011
public bool IsValidatableEnum { get; }
1112
public bool HasStringBasedValidateMethod { get; }
1213

@@ -16,6 +17,7 @@ public ParsableGeneratorState(
1617
ValidationErrorState validationError,
1718
bool skipIParsable,
1819
bool isKeyMemberParsable,
20+
bool isEnum,
1921
bool isValidatableEnum,
2022
bool hasStringBasedValidateMethod)
2123
{
@@ -24,6 +26,7 @@ public ParsableGeneratorState(
2426
ValidationError = validationError;
2527
SkipIParsable = skipIParsable;
2628
IsKeyMemberParsable = isKeyMemberParsable;
29+
IsEnum = isEnum;
2730
IsValidatableEnum = isValidatableEnum;
2831
HasStringBasedValidateMethod = hasStringBasedValidateMethod;
2932
}
@@ -35,6 +38,7 @@ public bool Equals(ParsableGeneratorState other)
3538
&& ValidationError.Equals(other.ValidationError)
3639
&& SkipIParsable == other.SkipIParsable
3740
&& IsKeyMemberParsable == other.IsKeyMemberParsable
41+
&& IsEnum == other.IsEnum
3842
&& IsValidatableEnum == other.IsValidatableEnum
3943
&& HasStringBasedValidateMethod == other.HasStringBasedValidateMethod;
4044
}
@@ -53,6 +57,7 @@ public override int GetHashCode()
5357
hashCode = (hashCode * 397) ^ ValidationError.GetHashCode();
5458
hashCode = (hashCode * 397) ^ SkipIParsable.GetHashCode();
5559
hashCode = (hashCode * 397) ^ IsKeyMemberParsable.GetHashCode();
60+
hashCode = (hashCode * 397) ^ IsEnum.GetHashCode();
5661
hashCode = (hashCode * 397) ^ IsValidatableEnum.GetHashCode();
5762
hashCode = (hashCode * 397) ^ HasStringBasedValidateMethod.GetHashCode();
5863

0 commit comments

Comments
 (0)