Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support structurally closed hierarchies like Discriminated Unions based on C# records #44

Open
csharper2010 opened this issue Apr 5, 2022 · 4 comments
Labels
enhancement New feature or request

Comments

@csharper2010
Copy link
Contributor

csharper2010 commented Apr 5, 2022

Based on the work for #42 it would be nice to have the same feature also für discriminated unions implemented the C# record syntax.

public abstract record Result<TSuccess, TError> {
    private Result() { }

    public sealed record Success(TSuccess Value) : Result<TSuccess, TError>;

    public sealed record Error(TError value) : Result<TSuccess, TError>;
}

A switch with missing arms could also giving the analyzer error however there's a slight source of errors:

It is technically possible to subclass from the Result record outside of the scope because the C# record syntax for non-sealed records always creates a protected copy constructor. Anyway I would argue that the developer intention is clear as there are some intrinsics about what the copy constructor does and where it is used.

This line of code compiles even outside of the Result record but providing a concrete instance (Error in this case) does not make real sense as only the properties of the base record would be considered in the copy constructor:

public record OtherResult(string Value) : Result<string, string>(new Error(Value))

See also this StackOverflow question to see there are also other developers interested in such a feature.

As I understand it however, to really detect that situation, we must switch to Roslyn version 3.9.0 as the IsRecord property was added to the type symbol interface.

csharper2010 added a commit to csharper2010/ExhaustiveMatching that referenced this issue Apr 5, 2022
csharper2010 added a commit to csharper2010/ExhaustiveMatching that referenced this issue Apr 5, 2022
csharper2010 added a commit to csharper2010/ExhaustiveMatching that referenced this issue Apr 5, 2022
@jgilchrist
Copy link

@csharper2010 This is great!

@WalkerCodeRanger I've tested this with our projects and it works well FWIW. We'd love to have this in, it would be super useful as we use this pattern pretty extensively.

@dcuccia
Copy link

dcuccia commented Apr 27, 2022

Very nice - look forward to giving this a try, thanks!

@WalkerCodeRanger WalkerCodeRanger added the enhancement New feature or request label May 16, 2022
@MelbourneDeveloper
Copy link

MelbourneDeveloper commented Oct 27, 2024

I've been playing with something very similar. I was going to create a new issue but found this one. This is my example:

#pragma warning disable CS8509,IDE0072 

using ExhaustiveMatching;

namespace Exhaustion;

[Closed(typeof(Ok<,>), typeof(Error<,>))]
public abstract class Result<TSuccess, TError> { }

public sealed class Ok<TSuccess, TError>(TSuccess successValue) : Result<TSuccess, TError>
{
    public TSuccess SuccessValue { get; } = successValue;
}

public sealed class Error<TSuccess, TError>(TError errorValue) : Result<TSuccess, TError>
{
    public TError ErrorValue { get; } = errorValue;
}

public static class ResultExtensions
{
    public static string Match(Result<string, string> result) =>
        result switch
        {
            Ok<string, string> ok => ok.SuccessValue,
            Error<string, string> error => error.ErrorValue,
        };
}

@WalkerCodeRanger @csharper2010

I get these analyser errors:

[{
"resource": "/Users/christian/Documents/Code/RestClient.Net2/PatternTests/UnitTest1 copy 2.cs",
"owner": "DocumentAnalyzerSemantic",
"code": "EM0013",
"severity": 8,
"message": "Closed type case is not a subtype: Exhaustion.Ok",
"startLineNumber": 9,
"startColumn": 16,
"endLineNumber": 9,
"endColumn": 21
},{
"resource": "/Users/christian/Documents/Code/RestClient.Net2/PatternTests/UnitTest1 copy 2.cs",
"owner": "DocumentAnalyzerSemantic",
"code": "EM0013",
"severity": 8,
"message": "Closed type case is not a subtype: Exhaustion.Error",
"startLineNumber": 9,
"startColumn": 31,
"endLineNumber": 9,
"endColumn": 39
},{
"resource": "/Users/christian/Documents/Code/RestClient.Net2/PatternTests/UnitTest1 copy 2.cs",
"owner": "DocumentAnalyzerSemantic",
"code": "EM0011",
"severity": 8,
"message": "Exhaustion.Ok is not a case of its closed supertype: Exhaustion.Result",
"startLineNumber": 12,
"startColumn": 21,
"endLineNumber": 12,
"endColumn": 23
},{
"resource": "/Users/christian/Documents/Code/RestClient.Net2/PatternTests/UnitTest1 copy 2.cs",
"owner": "DocumentAnalyzerSemantic",
"code": "EM0011",
"severity": 8,
"message": "Exhaustion.Error is not a case of its closed supertype: Exhaustion.Result",
"startLineNumber": 17,
"startColumn": 21,
"endLineNumber": 17,
"endColumn": 26
}]

@MelbourneDeveloper
Copy link

So it looks like there has been no movement on this for a couple of years...

It's a shame if this project is on pause because it's a really great project, but thanks anyway

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants