added Extend extension methods generation
This commit is contained in:
@@ -13,6 +13,7 @@ public class ExtensionsMethodGenerator : IIncrementalGenerator
|
||||
new ResultMapExecutor(),
|
||||
new ResultBindExecutor(),
|
||||
new ResultTapExecutor(),
|
||||
new ResultExtendExecutor(),
|
||||
new ResultTryRecoverExecutor(),
|
||||
new ResultAppendExecutor(),
|
||||
new TryExtensionsExecutor(),
|
||||
|
||||
@@ -460,12 +460,4 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
internal static string JoinArguments(string arg1, string arg2) => (arg1, arg2) switch
|
||||
{
|
||||
("", "") => "",
|
||||
(string arg, "") => arg,
|
||||
("", string arg) => arg,
|
||||
_ => $"{arg1}, {arg2}"
|
||||
};
|
||||
}
|
||||
|
||||
160
Railway.SourceGenerator/ResultExtendExecutor.cs
Normal file
160
Railway.SourceGenerator/ResultExtendExecutor.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Just.Railway.SourceGen;
|
||||
|
||||
internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
||||
{
|
||||
protected override string ExtensionType => "Extend";
|
||||
|
||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||
{
|
||||
if (argCount == 0 || argCount == Constants.MaxResultTupleSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var templateArgNames = Enumerable.Range(1, argCount)
|
||||
.Select(i => $"T{i}")
|
||||
.ToImmutableArray();
|
||||
|
||||
var expandedTemplateArgNames = templateArgNames.Add("R");
|
||||
string resultTypeDef = GenerateResultTypeDef(templateArgNames);
|
||||
string resultValueExpansion = GenerateResultValueExpansion(templateArgNames);
|
||||
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
||||
string methodTemplateDecl = GenerateTemplateDecl(expandedTemplateArgNames);
|
||||
string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result<R>"));
|
||||
|
||||
sb.AppendLine($"#region {resultTypeDef}");
|
||||
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static {{resultExpandedTypeDef}} Extend{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{bindTemplateDecl}} extensionFunc)
|
||||
{
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(result));
|
||||
}
|
||||
else if (result.IsFailure)
|
||||
{
|
||||
return result.Error!;
|
||||
}
|
||||
|
||||
var extension = extensionFunc({{resultValueExpansion}});
|
||||
if (extension.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(extensionFunc));
|
||||
}
|
||||
else if (extension.IsFailure)
|
||||
{
|
||||
return extension.Error!;
|
||||
}
|
||||
|
||||
return Result.Success({{JoinArguments(resultValueExpansion, "extension.Value")}});
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
var expandedTemplateArgNames = templateArgNames.Add("R");
|
||||
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
||||
string methodTemplateDecl = GenerateTemplateDecl(expandedTemplateArgNames);
|
||||
string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result<R>"));
|
||||
string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add($"{taskType}<Result<R>>"));
|
||||
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{bindTemplateDecl}} extensionFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(resultTask));
|
||||
}
|
||||
else if (result.IsFailure)
|
||||
{
|
||||
return result.Error!;
|
||||
}
|
||||
|
||||
var extension = extensionFunc({{resultValueExpansion}});
|
||||
if (extension.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(extensionFunc));
|
||||
}
|
||||
else if (extension.IsFailure)
|
||||
{
|
||||
return extension.Error!;
|
||||
}
|
||||
|
||||
return Result.Success({{JoinArguments(resultValueExpansion, "extension.Value")}});
|
||||
}
|
||||
""");
|
||||
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} extensionFunc)
|
||||
{
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(result));
|
||||
}
|
||||
else if (result.IsFailure)
|
||||
{
|
||||
return result.Error!;
|
||||
}
|
||||
|
||||
var extension = await extensionFunc({{resultValueExpansion}}).ConfigureAwait(false);
|
||||
if (extension.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(extensionFunc));
|
||||
}
|
||||
else if (extension.IsFailure)
|
||||
{
|
||||
return extension.Error!;
|
||||
}
|
||||
|
||||
return Result.Success({{JoinArguments(resultValueExpansion, "extension.Value")}});
|
||||
}
|
||||
""");
|
||||
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} extensionFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(resultTask));
|
||||
}
|
||||
else if (result.IsFailure)
|
||||
{
|
||||
return result.Error!;
|
||||
}
|
||||
|
||||
var extension = await extensionFunc({{resultValueExpansion}}).ConfigureAwait(false);
|
||||
if (extension.State == ResultState.Bottom)
|
||||
{
|
||||
throw new ResultNotInitializedException(nameof(extensionFunc));
|
||||
}
|
||||
else if (extension.IsFailure)
|
||||
{
|
||||
return extension.Error!;
|
||||
}
|
||||
|
||||
return Result.Success({{JoinArguments(resultValueExpansion, "extension.Value")}});
|
||||
}
|
||||
""");
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,13 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor
|
||||
1 => $"Result<{string.Join(", ", templateArgNames)}>",
|
||||
_ => $"Result<({string.Join(", ", templateArgNames)})>",
|
||||
};
|
||||
protected static string JoinArguments(string arg1, string arg2) => (arg1, arg2) switch
|
||||
{
|
||||
("", "") => "",
|
||||
(string arg, "") => arg,
|
||||
("", string arg) => arg,
|
||||
_ => $"{arg1}, {arg2}"
|
||||
};
|
||||
|
||||
protected static string GenerateResultValueExpansion(ImmutableArray<string> templateArgNames)
|
||||
{
|
||||
|
||||
@@ -144,7 +144,7 @@ public abstract class Error : IEquatable<Error>, IComparable<Error>
|
||||
message = Message;
|
||||
}
|
||||
|
||||
[Pure] internal virtual Error AccessUnsafe(int position) => this;
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] internal virtual Error AccessUnsafe(int position) => this;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(ExpectedErrorJsonConverter))]
|
||||
@@ -393,7 +393,7 @@ public sealed class ManyErrors : Error, IEnumerable<Error>, IReadOnlyList<Error>
|
||||
errors.Add(error);
|
||||
}
|
||||
|
||||
[Pure] internal override Error AccessUnsafe(int position) => _errors[position];
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] internal override Error AccessUnsafe(int position) => _errors[position];
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed class ErrorJsonConverter : JsonConverter<Error>
|
||||
|
||||
if (!(reader.TokenType == JsonTokenType.String))
|
||||
throw new JsonException("Unable to deserialize Error type.");
|
||||
|
||||
|
||||
var propvalue = reader.GetString();
|
||||
if (string.IsNullOrEmpty(propvalue))
|
||||
break;
|
||||
@@ -84,7 +84,7 @@ public sealed class ErrorJsonConverter : JsonConverter<Error>
|
||||
else if (!string.IsNullOrEmpty(propname))
|
||||
{
|
||||
extensionData ??= ImmutableDictionary.CreateBuilder<string, string>();
|
||||
extensionData.Add(propname, propvalue);
|
||||
extensionData[propname] = propvalue;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -7,7 +7,13 @@ internal enum ResultState : byte
|
||||
|
||||
public readonly partial struct Result : IEquatable<Result>
|
||||
{
|
||||
internal SuccessUnit Value => new();
|
||||
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Simplified source generation")]
|
||||
internal SuccessUnit Value
|
||||
{
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new();
|
||||
}
|
||||
|
||||
internal readonly Error? Error;
|
||||
internal readonly ResultState State;
|
||||
|
||||
@@ -36,12 +42,18 @@ public readonly partial struct Result : IEquatable<Result>
|
||||
public static Result<(T1, T2, T3, T4, T5)> Success<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) => new((value1, value2, value3, value4, value5));
|
||||
|
||||
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result Failure(string error) => Error.New(error ?? throw new ArgumentNullException(nameof(error)));
|
||||
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
||||
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result Failure(Exception exception) => new(Error.New(exception) ?? throw new ArgumentNullException(nameof(exception)));
|
||||
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result<T> Failure<T>(string error) => Error.New(error ?? throw new ArgumentNullException(nameof(error)));
|
||||
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result<T> Failure<T>(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
||||
|
||||
@@ -66,13 +78,13 @@ public readonly partial struct Result : IEquatable<Result>
|
||||
[Pure] public bool IsSuccess => Error is null;
|
||||
[Pure] public bool IsFailure => Error is not null;
|
||||
|
||||
[Pure] public bool Success([MaybeNullWhen(false)]out SuccessUnit? u, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
||||
[Pure] public bool TryGetValue([MaybeNullWhen(false)]out SuccessUnit? u, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case ResultState.Success:
|
||||
u = new SuccessUnit();
|
||||
error = default;
|
||||
error = null;
|
||||
return true;
|
||||
|
||||
case ResultState.Error:
|
||||
@@ -161,13 +173,13 @@ public readonly struct Result<T> : IEquatable<Result<T>>
|
||||
[Pure] public bool IsSuccess => State == ResultState.Success;
|
||||
[Pure] public bool IsFailure => State == ResultState.Error;
|
||||
|
||||
[Pure] public bool Success([MaybeNullWhen(false)]out T value, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
||||
[Pure] public bool TryGetValue([MaybeNullWhen(false)]out T value, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case ResultState.Success:
|
||||
value = Value;
|
||||
error = default;
|
||||
error = null;
|
||||
return true;
|
||||
|
||||
case ResultState.Error:
|
||||
|
||||
@@ -22,7 +22,7 @@ public class GeneralUsage
|
||||
return "";
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Assert.Equal("TEST_1;SOME", result);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class GeneralUsage
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
|
||||
|
||||
// When
|
||||
|
||||
var result = Result.Success()
|
||||
@@ -94,7 +94,7 @@ public class GeneralUsage
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
|
||||
|
||||
// When
|
||||
|
||||
var result = await Result.Success()
|
||||
@@ -169,4 +169,47 @@ public class GeneralUsage
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(error, result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenExtendingSuccessWithSuccess_ShouldReturnSuccess()
|
||||
{
|
||||
var success = Result.Success(1)
|
||||
.Append("2");
|
||||
|
||||
var result = success
|
||||
.Extend((i, s) => Result.Success($"{i} + {s}"));
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal((1, "2", "1 + 2"), result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenExtendingFailureWithSuccess_ShouldNotEvaluateExtension()
|
||||
{
|
||||
var failure = Result.Success(1)
|
||||
.Append(Result.Failure<string>("failure"));
|
||||
|
||||
var result = failure
|
||||
.Extend((i, s) =>
|
||||
{
|
||||
Assert.Fail();
|
||||
return Result.Success("");
|
||||
});
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(Error.New("failure"), result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenExtendingSuccessWithFailure_ShouldReturnFailure()
|
||||
{
|
||||
var success = Result.Success(1)
|
||||
.Append("2");
|
||||
|
||||
var result = success
|
||||
.Extend((i, s) => Result.Failure<string>("failure"));
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(Error.New("failure"), result.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user