{"id":15011650,"url":"https://github.com/walkercoderanger/exhaustivematching","last_synced_at":"2025-04-12T03:31:05.519Z","repository":{"id":35114958,"uuid":"208287029","full_name":"WalkerCodeRanger/ExhaustiveMatching","owner":"WalkerCodeRanger","description":"C# Analyzer Adding Exhaustive Checking of Switch Statements and Expressions","archived":false,"fork":false,"pushed_at":"2023-11-19T10:12:58.000Z","size":4044,"stargazers_count":80,"open_issues_count":20,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-25T23:23:59.937Z","etag":null,"topics":["csharp-library","discriminated-unions","exhaustiveness-checking","nuget-package","roslyn-analyzer"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WalkerCodeRanger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-13T14:58:41.000Z","updated_at":"2025-01-10T03:26:27.000Z","dependencies_parsed_at":"2024-06-18T17:10:56.966Z","dependency_job_id":"9e9e5819-748f-4523-92ec-9ec11b0c996a","html_url":"https://github.com/WalkerCodeRanger/ExhaustiveMatching","commit_stats":{"total_commits":141,"total_committers":3,"mean_commits":47.0,"dds":0.04255319148936165,"last_synced_commit":"0050b02c463c1c1a165285162a42f2c4a55ea1e9"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WalkerCodeRanger%2FExhaustiveMatching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WalkerCodeRanger%2FExhaustiveMatching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WalkerCodeRanger%2FExhaustiveMatching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WalkerCodeRanger%2FExhaustiveMatching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WalkerCodeRanger","download_url":"https://codeload.github.com/WalkerCodeRanger/ExhaustiveMatching/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248512509,"owners_count":21116615,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp-library","discriminated-unions","exhaustiveness-checking","nuget-package","roslyn-analyzer"],"created_at":"2024-09-24T19:41:23.569Z","updated_at":"2025-04-12T03:31:02.806Z","avatar_url":"https://github.com/WalkerCodeRanger.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ExhaustiveMatching.Analyzer\r\n\r\n`ExhaustiveMatching.Analyzer` adds exhaustive matching to C# switch statements\r\nand expressions.\r\n\r\n*Get compiler errors for missing cases in a switch statement or expression.*\r\nMark which switches should have exhaustiveness checking by throwing an exception\r\nin the default case. Exhaustiveness checking works not just for enums, but for\r\nclasses and interfaces. Turn them into [discriminated unions (aka sum\r\ntypes)](https://en.wikipedia.org/wiki/Tagged_union) by marking them with the\r\n`Closed` attribute and listing the cases. `ExhaustiveMatching.Analyzer` goes\r\nbeyond what other languages support by handling full inheritance hierarchies.\r\n\r\n## Quickstart Guide\r\n\r\nMark a switch statement or expression as exhaustive and get errors for missing\r\ncases.\r\n\r\n```csharp\r\nusing ExhaustiveMatching;\r\n\r\npublic enum CoinFlip { Heads, Tails }\r\n\r\n// ERROR Enum value not handled by switch: Tails\r\nswitch (coinFlip)\r\n{\r\n    default:\r\n        throw ExhaustiveMatch.Failed(coinFlip);\r\n    case CoinFlip.Heads:\r\n        Console.WriteLine(\"Heads!\");\r\n        break;\r\n}\r\n\r\n// ERROR Enum value not handled by switch: Tails\r\n_ = coinFlip switch\r\n{\r\n    CoinFlip.Heads =\u003e \"Heads!\",\r\n    _ =\u003e throw ExhaustiveMatch.Failed(coinFlip),\r\n};\r\n```\r\n\r\nCreate [discriminated unions (aka sum types)](https://en.wikipedia.org/wiki/Tagged_union)\r\nand get errors for missing switch cases.\r\n\r\n```csharp\r\n[Closed(typeof(IPv4Address), typeof(IPv6Address))]\r\npublic abstract class IPAddress { … }\r\n\r\npublic class IPv4Address : IPAddress { … }\r\npublic class IPv6Address : IPAddress { … }\r\n\r\n// ERROR Subtype not handled by switch: IPv6Address\r\nswitch (ipAddress)\r\n{\r\n    default:\r\n        throw ExhaustiveMatch.Failed(ipAddress);\r\n    case IPv4Address ipv4Address:\r\n        return ipv4Address.MapToIPv6();\r\n}\r\n```\r\n\r\n## Packages and Downloading\r\n\r\nAll packages are available on [NuGet.org](https://www.nuget.org). There are\r\nthree packages available for different situations.\r\n\r\n* [ExhaustiveMatching.Analyzer](https://www.nuget.org/packages/ExhaustiveMatching.Analyzer/):\r\n  The standard package that includes all analyzers. Adds a dependency on\r\n  `ExhaustiveMatching.Analyzer` to your project.\r\n* `ExhaustiveMatching.Analyzer.Enums` *(Not Yet Implemented)*: Only includes the\r\n  exhaustive matching analyzer for enums using `InvalidEnumArgumentException`.\r\n  Avoids adding any dependencies or additional code to your project.\r\n* `ExhaustiveMatching.Analyzer.Source` *(Not Yet Implemented)*: For advanced\r\n  scenarios, avoids adding dependencies to your project while supporting most\r\n  analyzers by injecting code into your project. See [Dependency Free\r\n  Usage](#dependency-free-usage) for details.\r\n\r\n## Versioning and Compatibility\r\n\r\n*This package does not use [semantic versioning](https://semver.org).* Instead,\r\nthe first two version numbers match the major and minor version of the\r\n`Microsoft.CodeAnalysis` package referenced by the analyzer. This determines the\r\nversion of Visual Studio, MSBuild, etc. the analyzer is compatible with. The\r\nnext two version numbers are the major and minor versions of this package. At\r\nany time, multiple versions of Visual Studio may be actively supported. You\r\nshould use the most recent version compatible with the environment you are\r\nusing. If an older version is used, functionality will be missing.\r\n\r\nYour project must target a framework [compatible with .NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version).\r\nIn addition, a minimum version of Visual studio or .NET Core is required\r\ndepending on the version of the package you are using. See the table below.\r\n\r\n| Package Version      | Minimum Visual Studio Version                  | C# Language |\r\n| -------------------- | ---------------------------------------------- | ----------- |\r\n| 3.8.*major*.*minor*  | Visual Studio 2019 version 16.8, .NET 5        | C# 9        |\r\n| 3.3.*major*.*minor*  | Visual Studio 2019 version 16.3, .NET Core 3.0 | C# 8        |\r\n| 2.10.*major*.*minor* | Visual Studio 2017 version 15.9                | C# 7.3      |\r\n| 1.3.*major*.*minor*  | Visual Studio 2015 Update 3                    | C# 6.0      |\r\n| 0.*x*                | Visual Studio 2019 version 16.3, .NET Core 3.0 | C# 8        |\r\n\r\n## Usage\r\n\r\nInstall the `ExhaustiveMatching.Analyzer` package into each project that will\r\ncontain exhaustive switch statements, switch expressions, or the classes and\r\ninterfaces that will be switched on. Additionally, *install the package in any\r\nproject that will reference a project containing types marked with the `Closed`\r\nattribute*. This is important because the analyzer enforces rules about\r\ninheriting from and implementing closed types. If the analyzer isn't in a\r\nproject then those rules may be violated without an error being reported. Most\r\nof the time, the `ExhaustiveMatching.Analyzer` can be added to every project in\r\na solution.\r\n\r\nAs soon as you add the NuGet package to your project, the IDE (Microsoft Visual\r\nStudio, JetBrains Rider and possibly others) should automatically enable the\r\nanalyzer and start marking non-exhaustive switches in your code. Analogously,\r\nMSBuild and dotnet CLI should report the errors with no additional set up. If\r\nthis is not working, you may not be using a compatible version. See [Versioning\r\nand Compatibility](#versioning-and-compatibility) for more information.\r\n\r\n### Exhaustive Switch on Enum Values\r\n\r\nTo enable exhaustiveness checking for a switch on an enum, throw an\r\n`ExhaustiveMatchFailedException` from the default case. That exception is\r\nconstructed using the `ExhaustiveMatch.Failed(…)` factory method which should be\r\npassed the value being switched on. For switches with exhaustiveness checking,\r\nthe analyzer will report an error for any missing enum cases.\r\n\r\n```csharp\r\n// ERROR Enum value not handled by switch: Sunday\r\nswitch(dayOfWeek)\r\n{\r\n    default:\r\n       throw ExhaustiveMatch.Failed(dayOfWeek);\r\n    case DayOfWeek.Monday:\r\n    case DayOfWeek.Tuesday:\r\n    case DayOfWeek.Wednesday:\r\n    case DayOfWeek.Thursday:\r\n    case DayOfWeek.Friday:\r\n        Console.WriteLine(\"Weekday\");\r\n        break;\r\n    case DayOfWeek.Saturday:\r\n        // Omitted Sunday\r\n        Console.WriteLine(\"Weekend\");\r\n        break;\r\n}\r\n```\r\n\r\nExhaustiveness checking is also applied to switches that throw\r\n`InvalidEnumArgumentException`. This exception indicates that the value doesn't\r\nmatch any of the defined enum values. Thus, if the code throws it from the\r\ndefault case, the developer is expecting that all defined enum cases will be\r\nhandled by the switch. Using this exception, the throw statement in the above\r\nexample would be `throw new InvalidEnumArgumentException(nameof(dayOfWeek),\r\n(int)dayOfWeek, typeof(DayOfWeek));`. Since this is longer and less readable,\r\nits use is discouraged.\r\n\r\n### Exhaustive Switch on Type\r\n\r\nC# 7.0 added pattern matching including the ability to switch on the type of a\r\nvalue. To ensure any possible value will be handled, all subtypes must be\r\nmatched by some case. That is what exhaustiveness checking ensures.\r\n\r\nTo enable exhaustiveness checking for a switch on type, two things must be done.\r\nThe default case must throw an `ExhaustiveMatchFailedException` (using the\r\n`ExhaustiveMatch.Failed(…)` factory method) and the type being switched on must\r\nbe marked with the `Closed` attribute. The closed attribute makes a type similar\r\nto an enum by giving it a defined set of possible cases. However, instead of a\r\nfixed set of values like an enum, a closed type has a fixed set of direct\r\nsubtypes.\r\n\r\n**CAUTION:** subtyping rules for types with the `[Closed(...)]` attribute are\r\nenforced by the analyzer, not the language. They can be circumvented. To prevent\r\nthis include the exhaustive matching analyzer in all projects in your solution,\r\ndo not expose a closed type to external code that may not be using exhaustive\r\nmatching, and do not use dynamic code generation to create subtypes. If\r\nunexpected subtypes are created, an `ExhaustiveMatchFailedException` exception\r\ncould be generated at runtime.\r\n\r\nThis example shows how to declare a closed class `Shape` that can be either a\r\ncircle or a square.\r\n\r\n```csharp\r\n[Closed(typeof(Circle), typeof(Square))]\r\npublic abstract class Shape { … }\r\n\r\npublic class Circle : Shape { … }\r\npublic class Square : Shape { … }\r\n```\r\n\r\nA switch on the type of a shape can then be checked for exhaustiveness.\r\n\r\n```csharp\r\nswitch(shape)\r\n{\r\n    case Circle _:\r\n        Console.WriteLine(\"Circle\");\r\n        break;\r\n    case Square _:\r\n        Console.WriteLine(\"Square\");\r\n        break;\r\n    default:\r\n        throw ExhaustiveMatch.Failed(shape);\r\n}\r\n```\r\n\r\n### Handling Null\r\n\r\nSince C# reference types are always nullable, but may be intended to never be\r\nnull, exhaustiveness checking does not require a case for null. If a null value\r\nis expected it can be handled by a `case null:`. The analyzer will ignore this\r\ncase for its analysis.\r\n\r\nFor nullable enum types, the analyzer requires that there be a `case null:` to\r\nhandle the null value.\r\n\r\n### Type Hierarchies\r\n\r\nWhile a given closed type can only have its direct subtypes as cases, some of\r\nthose subtypes may themselves be closed types. This allows for flexible\r\nswitching on multiple levels of a type hierarchy. The exhaustiveness check\r\nensures that every possible value is handled by some case. However, a single\r\ncase high up in the hierarchy can handle many types.\r\n\r\nIn the example below, an expression tree is being evaluated. The switch is able\r\nto match against multiple levels of the hierarchy while exhaustiveness checking\r\nensures no cases are missing. Notice how the `Addition` and `Subtraction` cases\r\nare indirect subtypes of `Expression`, and the `Value` case handles both\r\n`Constant` and `Variable`. This kind of sophisticated multi-level switching is\r\nnot supported in most languages that include exhaustive matching.\r\n\r\n```csharp\r\n[Closed(typeof(BinaryOperator), typeof())]\r\npublic abstract class Expression { … }\r\n\r\n[Closed(typeof(Addition), typeof(Subtraction))]\r\npublic abstract class BinaryOperator { … }\r\n\r\npublic class Addition { … }\r\npublic class Subtraction { … }\r\n\r\npublic abstract class Value { … }\r\n\r\npublic class Constant { … }\r\npublic class Variable { … }\r\n\r\npublic int Evaluate(Expression expression)\r\n{\r\n    switch(expression)\r\n    {\r\n        case Addition a:\r\n            return Evaluate(a.Left) + Evaluate(a.Right);\r\n        case Subtraction s:\r\n            return Evaluate(s.Left) - Evaluate(s.Right);\r\n        case Value v: // handles both Constant and Variable\r\n            return v.GetValue();\r\n        default:\r\n            throw ExhaustiveMatch.Failed(expression);\r\n    }\r\n}\r\n```\r\n\r\n## Analyzer Errors\r\n\r\nThe analyzer reports various errors for incorrect code. The table below gives a\r\ncomplete list of them along with a description.\r\n\r\n\u003ctable\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eNumber\u003c/th\u003e\r\n        \u003cth\u003eDescription\u003c/th\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0001\u003c/th\u003e\r\n        \u003ctd\u003eA switch on an enum is missing a case\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0002\u003c/th\u003e\r\n        \u003ctd\u003eA switch on a nullable enum is missing a null case\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0003\u003c/th\u003e\r\n        \u003ctd\u003eA switch on type is missing a case\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0011\u003c/th\u003e\r\n        \u003ctd\u003eA concrete type is not listed as a case in a closed type it is a direct subtype of\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0012\u003c/th\u003e\r\n        \u003ctd\u003eA case type listed in the closed attribute is not a direct subtype of the closed type (though it is a subtype)\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0013\u003c/th\u003e\r\n        \u003ctd\u003eA case type listed in the closed attribute is not a subtype of the closed type\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0014\u003c/th\u003e\r\n        \u003ctd\u003eA concrete subtype of a closed type is not covered by some case\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0015\u003c/th\u003e\r\n        \u003ctd\u003eAn open interface is not listed as a case in a closed type it is a direct subtype of\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0100\u003c/th\u003e\r\n        \u003ctd\u003eAn exhaustive switch can't contain when guards\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0101\u003c/th\u003e\r\n        \u003ctd\u003eCase pattern is not supported\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0102\u003c/th\u003e\r\n        \u003ctd\u003eCan't do exhaustiveness checking for switch on a type that is not an enum and not closed\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0103\u003c/th\u003e\r\n        \u003ctd\u003eCase is for a type that is not in the closed type hierarchy\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0104\u003c/th\u003e\r\n        \u003ctd\u003eDuplicate 'Closed' attribute on type\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n    \u003ctr\u003e\r\n        \u003cth\u003eEM0105\u003c/th\u003e\r\n        \u003ctd\u003eDuplicate case type\u003c/td\u003e\r\n    \u003c/tr\u003e\r\n\u003c/table\u003e\r\n\r\n## Dependency Free Usage\r\n\r\n*(Not Yet Implemented)*\r\n\r\nIn some situations, it isn't desirable to add a dependency to your project. For\r\nexample, a published NuGet package may want to use exhaustive matching without\r\nadding a dependency on `ExhaustiveMatching.Analyzer` to their project. The\r\n`ExhaustiveMatching.Analyzer.Source` package supports these cases. It does so\r\nby injecting the source for the `ExhaustiveMatch` and `InternalClosedAttribute`\r\ndirectly into the project. This classes are marked `internal` and will not be\r\nvisible from outside of the project.\r\n\r\nThis package does not support the `ClosedAttribute` because it would not be safe\r\nto do so. If a closed class were publicly exposed from your project, then other\r\ncode could create new subclasses of the closed class thereby making switches on\r\nthe closed class non-exhaustive.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwalkercoderanger%2Fexhaustivematching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwalkercoderanger%2Fexhaustivematching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwalkercoderanger%2Fexhaustivematching/lists"}