Giter Club home page Giter Club logo

exhaustivematching's People

Contributors

csharper2010 avatar mristin avatar walkercoderanger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

exhaustivematching's Issues

Missing part of the readme -- how to actually run this analyzer?

Hi,
I am missing maybe the most obvious part in the readme -- how do I actually run this analyzer? I added it to the project -- is it supposed to automatically run in my IDE now (I use Jetbrains Rider)?

Is there a way to automatically run it in the continuous integration workflows? Just running MSBuild does not produce any errors.

Thanks for your help! (Once I know how to run it, I'll make a PR to document that in the readme since other users might also be lost.)

Support structurally closed hierarchies like Discriminated Unions

It would be helpful to detect the closed hierarchy of a class with private constructor and nested subclasses having either also a private constructor or a sealed modifier. This is a way to simulate the desparately missing discriminated unions safely in C#.

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

    public sealed class Success : Result<TSuccess, TError> {
        public TSuccess Value { get; }
        public Success(TSuccess value) { Value = value; }
    }

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

A switch expression like this should provide the error:

var x = result switch
{
    Result<string, string>.Error error => ""Error: "" + error,
    _ => throw ExhaustiveMatch.Failed(result),
};

This would be o.k.:

var x = result ◊1switch{
    Result<string, string>.Error error => ""Error: "" + error,
    Result<string, string>.Success success => ""Success: "" + success,
    _ => throw ExhaustiveMatch.Failed(result),
}

Transition to Better Term for Member Types

Currently, the subtypes of a closed type are called member types or union of types. There should be a better name for them. Perhaps case types? Change to use the new name consistently.

Can't Switch on Case Type in Middle of Heirarchy

If you create a hierarchy of interface case types three deep, you can't use the middle one as a switch case. That is given IRoot, ICase, and IValue each of which implements the previous with IRoot and ICase being closed types, you can't switch on ICase. An error is reported that it "is not a case type inheriting from type being matched".

It appears the issue is that it isn't a concrete type or a leaf type, so it doesn't get put into the list of concrete case types, but it should still be a valid case type. These concepts need to be split.

A closed non-abstract class itself shouldn't need to be handled

Hello

In my use case, some of the classes cannot be abstract. They are basic models used for deserialization

example

public enum Kingdom
{
    Animalia,
    Plantae,
}

public enum DogBreed 
{
    Pug,
    Maltese,
}

public enum CatBreed 
{
    Pug,
    Maltese,
}

public class Organism
{
     public Kingdom Kingdom {get; set;}
}

public class Dog : Organism
{
    public DogBreed Breed {get; set; }
}


public class Cat: Organism
{
       public CatBreed Breed {get; set; }
}

public class OrganismHandler
{
    public void Handle(Organism organism)
    {
        switch(organism)
        {
            case Dog dog:
                Handle(dog);
                break;

            case Cat cat:
                Handle(cat)
                break;
            
           // if I do not add a case for class Organism the analyzer complains
           // if I do, it does not complain if I do not add Cat or Dog
           // what I want is to not need to handle organism, would even be ok to get analysis erros if I did
            default: 
                throw ExhaustiveMatch.Failed(regionSpecificForm);
        }

        private void Handle(Dog dog)
        {
                 ....
        }
        
        private void Handle(Cat cat)
        {
                 ....
        }
    }
}

Document Mirror Hierarchy

I don't entirely remember what this was supposed to be about. However, it is referencing the unit tests MirrorHierarchy and MirrorHierarchyMustBeCovered, and the example MirrorExample in MirrorHierarchy.cs. I think the point is that using such mirror hierarchies can be confusing and the rules need to be documented. However, at the moment I don't remember the rules I had in mind.

See issue number #26

Strong name the assembly

I had a nasty runtime error just now in a strong-named project targeting .NET Framework 4.8 that uses this NuGet package, as strong-named assemblies transitively require all referenced assemblies to also have strong-names.

I can work-around it with the StrongNamer hack package, but I'd prefer it if this assembly were strong-named properly in the first place.

Microsoft explicitly recommend that libraries distributed as NuGet packages should be strong-named:

You should strong name your open-source .NET libraries. Strong naming an assembly ensures the most people can use it, and strict assembly loading only affects .NET Framework.

Self Type as Case Type Causes Infinite Loop

If a closed type declares itself to be one of its cases, this causes an infinite loop in the analyzer. The analyzer will correctly find an error that the listed type isn't a direct subtype, but other code can't handle this error case.

Support switch expression and statement matches with literal type cases

The following sitautions are valid switches on a shape.

The type names should be handled correctly by the exhaustive checker.

switch (shape)
{
    case Square square:
        Console.WriteLine(""Square: "" + square);
        break;
    case Circle circle:
        Console.WriteLine(""Circle: "" + circle);
        break;
    case Triangle: // label syntax
        Console.WriteLine(""Triangle!"");
        break;
    default:
        throw ExhaustiveMatch.Failed(shape);
}";

var result = shape switch
{
    Square square => ""Square: "" + square,
    Circle circle => ""Circle: "" + circle,
    Triangle => ""Triangle!"", // type name
    _ => throw ExhaustiveMatch.Failed(shape),
};";

Analyzer Exception For Invalid Code

It is possible to get exceptions from the analyzer when analyzer invalid code. In particular, I believe that an unrecognized ClosedAttribute can cause it to assume the constructor parameters are not params but individual types.

Create a Better Readme

Create a readme that leads with an example. Then goes on to how to install from nuget?

  • Coin flip is a good example of a standard enum
  • IPv6 and IPv4 could be a good object example
  • Need good hierarchy example

Allow Mirror Hierarchies

If you create an inheritance hierarchy of interfaces that are closed and a matching hierarchy of classes that implement those interfaces, you'll get errors that the classes aren't case-types of the closed interfaces. However, sometimes this should be allowed. For example:

[Closed(typeof(ISquare), typeof(ICircle))]
interface IShape { }
interface ISquare : IShape { }
interface ICircle : IShape { }

// Gives an error, but is fine as long as all subclasses implement one of the interfaces covered by the case types
abstract class Shape : IShape { }
class Square : Shape, ISquare { }
class Circle : Shape, ICircle { }

Errors Should Be Reported Against Switch Head, Not Body

Currently, a number of diagnostic errors highlight the entire switch statement, including the body. They should only highlight the switch head. Meaning they should highlight just the switch keyword. The switch expression is excluded because it could be long and may have other errors.

Exceptions in Analyzer

The analyzer still throws exceptions sometimes which show up as warnings in VS. I think it is because of invalid code, but I'm not sure. Things to try:

  • Unknown Type in Case Clause
  • Unknown Type in Case Class List

Support switch expression

Switch expressions are not being checked. That is a real issue when a suggested refactoring is changing to switch expressions.

[Suggestion] Allow optional opt-in for requiring null handling case

Hello!

First of all, I absolutely love this library. I really appreciate your work on it :)

I ran into a case today where I had refactored some code that previously had not allowed null to be a valid value, but now I have switch cases throwing ExhaustiveMatch.Failed. I realized it was because I had not been handling the null case in the switch.

In the documentation, I see you have the following:

Since C# reference types are always nullable, but may be intended to never be null, exhaustiveness checking does not require a case for null. If a null value is expected it can be handled by a case null:. The analyzer will ignore this case for its analysis.

For nullable enum types, the analyzer requires that there be a case null: to handle the null value.

I understand that this might not be desired as a default behavior, but I would really personally like to be forced to handle null on reference types.

Is there any way that we can see this as an opt-in thing in the future? Perhap something we can decorate our assembly with as an attribute, or similar.

Thank you!
Anthony

Should Support Open Hierarchies

Currently, every subtype of a closed type must be closed, sealed, or struct. Open type hierarchies should be supported, meaning that subtypes don't need to be any of those things. Instead, when matching against the closed type, only its immediate subtype and subtypes of a chain of closed types can be used to match against. That is because exhaustiveness can only be checked for closed types, however it is fine to use an open type to catch all possible subtypes of it.

Note: this adds new error(s) for switch cases trying to match subtypes whose immediate parent aren't closed.

THANK YOU!

This is an awesome package and I really appreciate your effort.

Add a "Why?" Section to Readme

The readme was planned to have a "Why?" section but it was dropped. Write one. Discussing the expression problem would be good.

Dependency-free builds

I'd love to use this ExhaustiveMatching analyzer with a number of NuGet projects that I work on but I understand that even though this is a "private" dependency, it still causes my project to have a reference to the ExhaustiveMatching.dll assembly which then must be referenced by my output NuGet package - this isn't ideal.

It should be possible for the ExhaustiveMatching code-analysis to work by detecting the use of built-in exception types (e.g. ArgumentOutOfRangeException) in a switch block provided they're annotated with a known magic const string for the message: ctor parameter - or allow use to redefine the ExhaustiveMatching exceptions in our own projects - and the analyzer could then look for that without us needing to reference the ExhaustiveMatching.dll assembly.


On a related note, I'd like to have two separate exceptions to : one derived from ArgumentOutOfRangeException for when switching over an enum parameter argument, and another derived from InvalidOperationException when swiching over an enum that isn't a parameter argument.

Add support for exhaustive matching on records

The analyzer does not handle record classes.
for example :

    [Closed(typeof(UserNotFound))]
    public abstract record Response
    {
        public record UserNotFound : Response;

        public record Success : Response;
    }

    class Controller
    {
        public int Get(Response response)
        {
            return response switch
            {
                Response.UserNotFound _ => -1,
                _ => throw ExhaustiveMatch.Failed(response)
            };
        }
    }

The analyzer doesn't detect that the "Success" class is needed to be added to the Closed attribute.
The analyzer doesn't detect that the "Success" clause needs to be handled in the Get() method

Thanks for this library, really appreciate the effort !

Report Error for Multiple Closed Attributes on Declaration

The ClosedAttribute is set AllowMultiple=true because we want to allow partial classes to have a closed attribute on each declaration of the class. However, we don't really want to allow multiple closed attributes on a single class declaration. That is on one of the partial class declarations. Issue an error from the analyzer for multiple closed attributes on a declaration.

NullReferenceException in analyzer

System.NullReferenceException: Object reference not set to an instance of an object.
    at ExhaustiveMatching.Analyzer.TypeSymbolExtensions.GetFullName(ISymbol symbol)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.GetTypeSymbolMatched(SyntaxNodeAnalysisContext context, ITypeSymbol type, CasePatternSwitchLabelSyntax casePattern, HashSet`1 allCases, Boolean isClosed)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.<>c__DisplayClass3_0.<AnalyzeSwitchOnClosed>b__1(CasePatternSwitchLabelSyntax casePattern)
    at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
    at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
    at System.Collections.Immutable.DisposableEnumeratorAdapter`2.MoveNext()
    at System.Collections.Immutable.ImmutableHashSet`1.Union(IEnumerable`1 other, MutationInput origin)
    at System.Collections.Immutable.ImmutableHashSet`1.Union(IEnumerable`1 items, Boolean avoidWithComparer)
    at System.Collections.Immutable.ImmutableHashSet`1.Union(IEnumerable`1 other)
    at System.Collections.Immutable.ImmutableHashSet.ToImmutableHashSet[TSource](IEnumerable`1 source, IEqualityComparer`1 equalityComparer)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.AnalyzeSwitchOnClosed(SyntaxNodeAnalysisContext context, SwitchStatementSyntax switchStatement, ITypeSymbol type)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.Analyze(SyntaxNodeAnalysisContext context, SwitchStatementSyntax switchStatement)
    at ExhaustiveMatching.Analyzer.ExhaustiveMatchAnalyzer.AnalyzeSwitchStatement(SyntaxNodeAnalysisContext context)

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

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.