├── .gitignore ├── LICENSE ├── README.md └── src ├── .editorconfig ├── Generators ├── Generator.cs ├── Generators.csproj ├── OneOfGenerator.cs └── UnionGenerator.cs ├── Scratch ├── MyUnion.cs ├── MyUnion_generated.cs ├── Program.cs └── Scratch.csproj ├── UnionGeneratorTests ├── MSTestSettings.cs ├── UnionGeneratorTests.cs └── UnionGeneratorTests.csproj ├── UnionSourceGenerator ├── UnionSourceGenerator.Package │ ├── ReadMe.Nuget.md │ ├── UnionSourceGenerator.Package.csproj │ ├── build_nuget.cmd │ ├── erase_installed_nuget.cmd │ └── tools │ │ ├── install.ps1 │ │ └── uninstall.ps1 └── UnionSourceGenerator │ ├── RoslynExtensions.cs │ ├── UnionSourceGenerator.cs │ └── UnionSourceGenerator.csproj ├── UnionSourceGeneratorTests ├── RoslynExtensions.cs ├── UnionSourceGeneratorTests.cs ├── UnionSourceGeneratorTests.csproj └── Usings.cs ├── UnionTypes.sln ├── UnionTypes ├── Decimal64.cs ├── IOneOf.cs ├── ITypeUnion.cs ├── None.cs ├── OneOf_generated.cs ├── OneOf_generated.tt ├── Option.cs ├── Option_generated.cs ├── Option_generated.tt ├── ReadMe.Nuget.md ├── Result.cs ├── Result_generated.cs ├── Result_generated.tt ├── TypeUnion.cs ├── TypeUnionExtensions.cs ├── TypeUnion_TryConvert.cs ├── UnionAttributes.cs ├── UnionTypes.csproj ├── Variant.cs └── build_nuget.cmd └── UnionTypesTests ├── ConversionTests.cs ├── MSTestSettings.cs ├── OneOfTests.cs ├── OptionTests.cs ├── ResultTests.cs ├── UnionTypesTests.csproj └── VariantTests.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnionTypes.Toolkit 2 | 3 | A library of common union types for dotnet and a C# source generator for creating custom ones. 4 | 5 | *disclaimer: This repo started as an exploration of union types for introduction into C# and the dotnet runtime 6 | as part of the C# LDM (Language Design Meeting). 7 | It is now published (by [me](https://github.com/mattwar)) for anyone to use, but probably mainly me. 8 | It is not a product of Microsoft, and is not an indicator of what will or will not become a product.* 9 | 10 | --- 11 | Table of Contents 12 | 13 | - [Overview](#Overview) 14 | - [Download the Toolkit and Generator](#Download-the-Toolkit-and-Generator) 15 | - [Using the Included Union Types](#Using-the-Included-Union-Types) 16 | - [Generating Custom Union Types](#Custom-Union-Types) 17 | 18 | ## Overview 19 | 20 | The term *Union Type* is used here to refer generally to many kinds of unions, 21 | sometimes called discriminated unions, sum types, tagged unions, etc. 22 | You may already be familiar with union types if you have used discriminated unions in F#, 23 | union types in Typescript or even a union structure in C++. 24 | 25 | In short, a union type is any type that can exist in one of many explicit states or cases. 26 | Each case may allow the type to hold onto different kinds of data. 27 | For example, a C# enum is a union type, since each enum value is a unique case of the enum type, 28 | but it is not a very interesting one because it does not carry any extra data with it. 29 | A class heirarchy is also a union type, since each derived class is a unique case of the base type 30 | and each can hold different kinds of data. If a class hierarchy is suitable for your needs, look no further. 31 | 32 | This toolkit deals with special kinds of union types that solve problems that are not satisfactorily solved by class hierarchies alone. 33 | Primarily these are cases where you are concerned about allocations or footprint (size of data). 34 | It focuses on two categories of these union types, but there may be others. 35 | 36 | - A *Type Union* is any type that can hold or represent a value of one of a set of unique types. 37 | For instance, if you wanted to have a variable assignable to only either a Cat, Dog or Bird, 38 | you could use a type union to constrain the value to only one of those types. 39 | If you already have a class hierarchy, for example Pet, of which all those types are derived from, then you don't need anything else. 40 | Just use the base type. However, if its not pratical to have a class hierachy of just the types you want to include, then a type union is a good alternative. 41 | 42 | - A *Tag Union* is not limited to a set of unique types, but is instead constrained to a set of uniquely named cases (tags) 43 | like an enum is, but each case may also carry with it its own unique set of variables. 44 | Tag unions (or traditional discriminated unions) originate from languages with a history of not having classes or inheritance. 45 | Typical usage patterns in those languages include constructing the union from cases and values and later matching on the case and deconstructing the values back into local variables. 46 | 47 | The types provided in the library are all presented as type unions to give you the option to interact with the case values outside of the union. 48 | They each implement the `ITypeUnion` interface that enables conditionally constructing them, accessing their values, and converting them 49 | to other type unions. Tag unions share no commonality that would make this possible. 50 | 51 | These types are provided because they are the ones the community typically asked for when discussing discriminated unions 52 | with the C# Language Design Team, and so got modelled for discussion. 53 | 54 | - The `OneOf` type is a family of generic struct types that can hold a single value constrained to the set of types declared in its type arguments. 55 | You may already have access to a type like this from another source or by a different name. 56 | This is a good choice for most use cases, when the types you want to include are not already together in a hierarchy and are primarily already reference types, 57 | but has the drawback of boxing value types, which could matter if your application is sensitive to GC pressure. 58 | 59 | - The `Option` type is a type that is often built into languages to represent a value that may or may not be present, 60 | similar to how some might use a null to represent the absence of a value. 61 | The benefit of the Option type is that you won't accidentally deference the null, causing an exception. 62 | Languages with an Option (or Maybe) type typically have monadic operations that ferry the absence of a value back through your code, 63 | automatically skipping that parts that would depend on the value without requiring you to constantly check. 64 | This is similar to how the null conditional operator works in C#, but at a grander scale. 65 | You won't be able to use it that way in C# but you can simulate a bit of it via some of the provided methods. 66 | 67 | - The `Result` type is a type that is often built into languages to represent the result of an operation that may fail. 68 | It represents a value that you return from a function that is either in the success state with its expected value or a failure state with an error. 69 | Typically, languages that have this type also have monadic operations that ferry the failure through your code 70 | without requiring you to explicitly unpack them to use the success value, similar to how you experience exceptions working in C#. 71 | Its not possible to use it that way in C# but you can simulate a bit of it via some of the provided methods. 72 | 73 | - The `Variant` type is a type union that is not actually constrained. 74 | It can hold a value of any type, but will not box most primitives and small structs. 75 | It does this by partially being a type union with a fixed number of known cases, 76 | and a catch-all case that also tries to avoid boxing at runtime for types that can fit in the provided space. 77 | It is a good choice when you would have otherwise chosen to use `object`, 78 | but want to avoid boxing in common scenarios. 79 | 80 | If none of these types seem suitable for your needs, 81 | or you'd rather have your own type with its own name than repeatedly typing out all the case types as generic arguments 82 | every time you refer to it, you can create a custom union type. 83 | To do this, you can either write a completely custom type following your own rules, 84 | write a custom type following the same patterns and interfaces provided in this library to allow interop with features provided, 85 | or use the source generator to create the type for you from a partial declaration. 86 | 87 | --- 88 | 89 | ## Download the Toolkit and Generator 90 | 91 | The toolkit is available as a nuget package on nuget.org. 92 | [Download Toolkit Here](https://www.nuget.org/packages/UnionTypes.Toolkit) 93 | 94 | A separate source generator for generating custom union types is also available as a nuget package. 95 | [Download Generator Here](https://www.nuget.org/packages/UnionTypes.Toolkit.Generator) 96 | 97 | --- 98 | 99 | ## Using the Included Union Types 100 | 101 | The toolkit library includes many predefined union types ready to use. 102 | 103 | - [OneOf](#Using-the-OneOf-Types) 104 | - [Option](#Using-the-Option-Type) 105 | - [Result](#Using-the-Result-Type) 106 | - [Variant](#Using-the-Variant-Type) 107 | 108 | Additional helper classes and interfaces: 109 | 110 | - ITypeUnion 111 | - TypeUnion 112 | 113 | --- 114 | ## Using the OneOf Types 115 | 116 | The `OneOf` type is a series of overloaded generic types. 117 | Each `OneOf` type is a closed type union, only allowing values of types specified in the type arguments. 118 | Use them to declare type unions of any class, struct, interface or array types. 119 | 120 | - Not supported: refs, ref structs and pointers. That means no `Span.` If you require types like `Span` use a custom union. The generator supports generating ref struct unions. 121 | 122 | - When instances of value types (structs in C#) are placed in the union, they are stored by boxing the value, which may cause GC pressure. 123 | 124 | ### Creating an Instance 125 | 126 | You can create an instance of a `OneOf` type by calling one of the `Create` factory methods or via assignment. 127 | 128 | ```CSharp 129 | var number = OneOf.Create(5); // using factory 130 | OneOf number = 5; // using assignment. 131 | ``` 132 | 133 | You can also attempt to create an instance using the `TryCreate` factory method that attempts to create an instance of the OneOf type 134 | from an arbitrary value. 135 | 136 | ```CSharp 137 | object someValue = 5; 138 | if (OneOf.TryCreate(someValue, out var number)) { ... }) 139 | ``` 140 | 141 | You can also use `TryCreate` to attempt to create a OneOf from a different type union. 142 | 143 | ```CSharp 144 | OneOf intOrString = 5; 145 | if (OneOf.TryCreate(intOrString, out var number)) { ... }) 146 | ``` 147 | 148 | ### Accessing the Value 149 | 150 | There are multiple ways to access the underlying value. 151 | 152 | You can access it in a weakly-typed way by using the `Value` property. 153 | ```CSharp 154 | object value = number.Value; 155 | ``` 156 | 157 | You can access it in a strongly-typed way via explicit coersion to one of the known types. 158 | However, this may throw an `InvalidCastException` if you use the wrong type. 159 | ```CSharp 160 | int x = (int)number; 161 | ``` 162 | 163 | You can access each possible strongly-typed value via additional value properties. 164 | Every `OneOf` type has a value property for each type argument; `Type1Value`, `Type2Value`, etc. 165 | You can determine which one of these is currently valid by checking the `Kind` property. 166 | Accessing an invalid value property will return default. 167 | ```CSharp 168 | var isLessThan5 = number.Kind switch { 169 | 1 => number.Type1Value < 5, 170 | 2 => number.Type2Value < 5.0 171 | _ => false 172 | }; 173 | ``` 174 | 175 | You can also use the `Select` and `Match` methods to check the case and access the value automatically. 176 | 177 | ```CSharp 178 | var isLessThan5 = number.Select( 179 | i => i < 5, 180 | d => d < 5.0 181 | ); 182 | ``` 183 | 184 | ```CSharp 185 | number.Match( 186 | i => Console.WriteLine($"less than 5: {i < 5}"), 187 | d => Console.WriteLine($"less than 5.0: {d < 5.0}") 188 | ); 189 | ``` 190 | 191 | Lastly, you can access arbitrarilly typed values via the `TryGet` method. 192 | ```CSharp 193 | if (number.TryGet(out var intValue)) { ... } 194 | ``` 195 | This technique is useful when you don't now the exact union type, and have a reference to `ITypeUnion`, 196 | or when you want to access the value using a base class or interface, or via a different union type. 197 | 198 | ```CSharp 199 | if (intOrDouble.TryGet>(out var intOrString)) { ... }) 200 | ``` 201 | 202 | ### Comparing Equality 203 | 204 | Each `OneOf` type declares pass-through equality operators and implements `IEquatable` so you can compare two instances of the same OneOf type. 205 | If the values are the same, the OneOf instances will be considered the same. 206 | 207 | ``` CSharp 208 | OneOf number1 = 5; 209 | OneOf number2 = "five"; 210 | if (number1 == number2) { ... } 211 | if (number1.Equals(number2)) { ... } 212 | ``` 213 | Since there are also implicit coercion operators, you can compare a OneOf instance with a value of one of the case types. 214 | 215 | ```CSharp 216 | OneOf number = 5; 217 | if (number == 5) { ... } 218 | if (number.Equals(5)) { ... } 219 | ``` 220 | 221 | You can also compare an instance of one `OneOf` type with an instance of another `OneOf` type, or any other type union that implements `ITypeUnion` 222 | using the generic `Equals` method. 223 | 224 | ```CSharp 225 | OneOf number = 5; 226 | OneOf value = 5; 227 | if (number.Equals(value)) { ... } 228 | ``` 229 | 230 | #### Compare equality between different unions 231 | 232 | ``` CSharp 233 | OneOf value1 = 5; 234 | OneOf value2 = 5; 235 | var areEqual = value1.Equals(value2); 236 | ``` 237 | 238 | --- 239 | ## Using the Option Type 240 | 241 | The `Option` type is a type that can hold a value of type `T` or no value at all. 242 | 243 | You create an instance of `Option` by calling the factory method `Some` or by just assigning a value to a variable with that type. 244 | 245 | ```CSharp 246 | var number = Option.Some(5); // using factory 247 | var number = Option.Some(5); // using factory and inferring the value type. 248 | Option number = 5; // using assignment. 249 | ``` 250 | 251 | You obtain a `Option` instance without a value by calling the the `None` factory, 252 | assigning a variable the `None.Singleton` instance or assigning a variable the default state. 253 | 254 | ```CSharp 255 | var noNumber = Option.None(); // using factory 256 | Option noNumber = None.Singleton; // using singleton 257 | Option noNumber = default; // using default 258 | ``` 259 | 260 | You access the state of the option by checking the `Kind` property and the value using the `Value` property. 261 | ```CSharp 262 | switch (number.Kind) 263 | { 264 | case Option.Case.Some: 265 | Console.WriteLine($"Its a value: {number.Value}"); 266 | break; 267 | case Option.Case.None: 268 | Console.WriteLine("Its nothing, really."); 269 | break; 270 | } 271 | ``` 272 | 273 | You can also use the `Match` and `Select` methods to handle each case. 274 | 275 | ```CSharp 276 | number.Match( 277 | value => Console.WriteLine($"Its a value: {value}"), 278 | () => Console.WriteLine("Its nothing, really.") 279 | ); 280 | ``` 281 | 282 | ```CSharp 283 | // convert non-values into values 284 | var newValue = number.Select(value => value * 2, () => 0); 285 | ``` 286 | 287 | There is also a `Map` method that allows you to transform the value, only if it currently exists. 288 | ```CSharp 289 | var mapped = number.Map(value => value * 2); 290 | ``` 291 | 292 | --- 293 | ## Using the Result Type 294 | 295 | The `Result` is a union that either holds a success value of type `TValue` or a failure value of type `TError`. 296 | 297 | You can construct one using the factory methods `Success` and `Failure`, or by assigning a value or error to a variable of that type. 298 | 299 | ```CSharp 300 | var result = Result.Success(5); // using factory 301 | Result result = 5; // using assignment. 302 | ``` 303 | ```CSharp 304 | var error = Result.Failure(new Error("Whoops")); // using factory 305 | Result error = new Error("Whoops"); // using assignment. 306 | ``` 307 | 308 | *Note: If both the value and the error type are the same, then assignment will be ambiguous and you wil need to use the factory methods.* 309 | 310 | Like with all the other unions, you can check the case via the `Kind` property and access the succesor or failure values 311 | using the `Value` and `Error` properties respectively. 312 | 313 | ```CSharp 314 | switch (result.Kind) 315 | { 316 | case Result.Case.Success: 317 | Console.WriteLine($"Success: {result.Value}"); 318 | break; 319 | case Result.Case.Failure: 320 | Console.WriteLine($"Failure: {result.Error.Message}"); 321 | break; 322 | } 323 | ``` 324 | 325 | Alternatively, you can use the `Match` and `Select` methods. 326 | 327 | ```CSharp 328 | result.Match( 329 | value => Console.WriteLine($"Success: {value}"), 330 | error => Console.WriteLine($"Failure: {error.Message}") 331 | ); 332 | ``` 333 | ```CSharp 334 | var mapped = result.Select(value => $"{value * 2}", error => -1); 335 | ``` 336 | 337 | In addition, there is a `Map` method that allow you to transform the success value 338 | only when there is a success value and otherwise maintain the error state. 339 | 340 | ```CSharp 341 | var mapped = result.Map(value => $"{value * 2}"); 342 | ``` 343 | 344 | --- 345 | ## Using the Variant Type 346 | 347 | The `Variant` type is a type union that is not closed to a fixed number of case types. 348 | Any value of any type can be assigned or converted into a `Variant`, even the value `null`. 349 | What makes a variant interesting is that it is designed to avoid boxing of many value and struct types. 350 | If the value or struct type constains no reference type members and fits withing 64 bits, it will be stored within the variant without boxing. 351 | 352 | ### Creating an Instance 353 | 354 | You can create an instance of a `Variant` by calling one of the `Create` factory methods or 355 | via assignment and implicit coercion if the value is one of the known primitives. 356 | 357 | ```CSharp 358 | var number = Variant.Create(5); // using factory 359 | Variant number = 5; // using assignment. 360 | Variant point = Variant.Create(new Point(3, 4)); // factory only if its not a known primitive. 361 | ``` 362 | 363 | You can also create a variant using the `TryCreate` method. However, it will always succeed, since variants may contain any value. 364 | It exists on the type to satisfy the contract of the `ITypeUnion` interface. 365 | 366 | ### Accessing the Value 367 | 368 | You can access the value in a weakly-typed way by using the `Value` property. 369 | However, this may cause boxing if the value is a struct type. 370 | 371 | ```CSharp 372 | object value = variant.Value; 373 | ``` 374 | 375 | You can access the strongly-typed value via any of the specialized value properties. 376 | The `Variant` type has a specialized value properties for many well known types; `Int32Value`, `DoubleValue`, `StringValue` etc. 377 | You can determine which of these is the correct property to access using the `Kind` property. 378 | 379 | ```CSharp 380 | var isLessThan5 = variant.Kind switch { 381 | VariantKind.Int32 => variant.Int32Value < 5, 382 | VariantKind.Double => variant.DoubleValue < 5.0, 383 | VariantKind.Int64 => variant.Int64Value < 5L, 384 | _ => false 385 | }; 386 | ``` 387 | 388 | You can attempt to access arbitrary strongly-typed values using the `TryGet` method. 389 | 390 | ```CSharp 391 | if (variant.TryGet(out var point)) { ... } 392 | ``` 393 | There is also `Get` that returns the value if possible or throws an `InvalidCastException`, 394 | `GetOrDefault` that returns the value if possible or `default`, 395 | and `CanGet` that tells you if the `TryGet` method will succeed. 396 | 397 | If you want to know the value's type without potential boxing, you can use the `Type` property. 398 | 399 | ```CSharp 400 | var currentType = variant.Type; 401 | ``` 402 | 403 | ### Nulls 404 | 405 | The `Variant` can store a null value. 406 | 407 | You can determine if the current value is `null`, by checking the `IsNull` property or by comparing the `Kind` property to `VariantKind.Null`. 408 | ```CSharp 409 | if (variant.IsNull) { ... } 410 | if (variant.Kind == VariantKind.Null) { ... } 411 | ``` 412 | 413 | *The `TryGet` and `CanGet` methods will return false when the value is null, even if the type `T` can contain a null value; for example, `int?` or `string?`.* 414 | 415 | 416 | --- 417 | ## Custom Union Types 418 | 419 | Custom union types can be generated for you using a C# source generator, given a partial type declartion. 420 | 421 | The source generator creates union types with efficient storage layouts. 422 | It does this by analyzing the data types involved and generating a layout that avoids boxing and minimizes the space needed to store the data from all the cases. 423 | It cannot be as optimal as the layout of C++ unions, since the runtime does not allow overlapping the same memory with 424 | reference and value types, but it does what it can to overlap and reuse fields. 425 | 426 | 427 | - [Choosing Between Type Unions and Tag Unions](#Choosing-Between-Type-Unions-and-Tag-Unions) 428 | - [Declaring a Type Union](#Declaring-a-Type-Union) 429 | - [Declaring a Tag Union](#Declaring-a-Tag-Union) 430 | - [Customizing the Generation with Attributes](#Customizing-the-Generation-with-Attributes) 431 | - [Declaring a Case Factory as a Property](#Declaring-a-Case-Factory-as-a-Property) 432 | - [Declaring a Case without an Accessor](#Declaring-a-Case-without-an-Accessor) 433 | - [Assigning Specific Tag Values to Cases](#Assigning-Specific-Tag-Values-to-Cases) 434 | - [Generate Union Types without the Toolkit](#Generating-Union-Types-without-the-Toolkit) 435 | - [Unions with Spans](#Unions-with-Spans) 436 | 437 | ### Choosing Between Type Unions and Tag Unions 438 | 439 | Both generated type unions and tag unions are very similar. 440 | Given the same cases and the same fundemental data, the two types will end up structurally identical internally. 441 | The difference lies in the methods and operators and interfaces provided. 442 | 443 | Since a type union is constrained to a set of unique types, it can provide more features than a tag union. 444 | The types included in the union are its cases, meaning multiple type unions can have some of the exact same cases 445 | and it is possible to have common generic interfaces to interact with the contents of any type union. 446 | 447 | This is not possible with a tag union. Since multiple cases, even with ones that only contain a single value, may contain values of the same type, 448 | knowing the value's type is not enough information to determine which case the value represents. 449 | Likewise, even having cases with the same tag names and the same kinds of values on two different unions is not enough 450 | to know that the two union's cases are meant to represent the same thing. It may just be coincidence. 451 | 452 | Specifically, a type union has these additional features: 453 | - Implicit coercion operators you can use to assign a value directly to the union without explicitly calling a factory method. 454 | - An implementation of the `ITypeUnion` interface enabling abstraction over your union and coercion between unions with similar cases. 455 | 456 | So, in short, if you want to know how to choose between them: 457 | 458 | - Use a type union when you want to constrain a variable to a set of types that are not already a class hierarchy containing just those types, 459 | and those types are useful in your application beyond just being cases of the union. 460 | 461 | - Use a tag union when the cases don't make sense in your application outside of the union, 462 | or you prefer the simplicity of the union without the additional operators, methods and interfaces. 463 | 464 | 465 | ### Declaring a Type Union 466 | 467 | You can declare a type union by declaring a partial struct with a `TypeUnion` attribute, 468 | and a partial factory method for each case type. 469 | 470 | *Be careful to not declare the type nested inside another type, or as part of a top-level statements file.* 471 | 472 | ```CSharp 473 | [TypeUnion] 474 | public partial struct Pet 475 | { 476 | public static partial Pet Create(Cat cat); 477 | public static partial Pet Create(Dog dog); 478 | public static partial Pet Create(Bird bird); 479 | } 480 | ``` 481 | 482 | As soon as its declared (and the source generator has successfully run) you can start using the type union. 483 | 484 | Assign a value directly to a variable. 485 | ```CSharp 486 | Pet pet = new Cat("Mr Fluffy"); 487 | ``` 488 | Alternatively, use the factory method to create the union. 489 | ```CSharp 490 | Cat cat = new Cat("Mr Fluffy"); 491 | Pet pet = Pet.Create(cat); 492 | ``` 493 | Switch on the case and access the value via strongly typed properties. 494 | ```CSharp 495 | switch (pet.Kind) 496 | { 497 | case Pet.Case.Cat: 498 | Console.WriteLine($"Cat's name is {pet.CatValue.Name}"); 499 | break; 500 | case Pet.Case.Dog: 501 | Console.WriteLine($"Dog's name is {pet.DogValue.Name}"); 502 | break; 503 | case Pet.Case.Bird: 504 | Console.WriteLine($"Bird's name is {pet.BirdValue.Name}"); 505 | break; 506 | } 507 | ``` 508 | By default the type will have a property named `Kind` that returns an enum named `Case` with names for each case taken from the case type itself. 509 | The values for each case can be accessed via strongly typed properties called `[case]Value`. 510 | 511 | Alternatively, you can use the `Match` and `Select` methods instead of a switch statement 512 | and skip referring to the kind and value properties, but potentially cause delegate allocations due to capture. 513 | 514 | ```CSharp 515 | pet.Match( 516 | cat => Console.WriteLine($"Cat's name is {cat.Name}"), 517 | dog => Console.WriteLine($"Dog's name is {dog.Name}"), 518 | bird => Console.WriteLine($"Bird's name is {bird.Name}") 519 | ); 520 | ``` 521 | 522 | ```CSharp 523 | var saying = pet.Select( 524 | cat => "meow", 525 | dog => "woof", 526 | bird => "chirp" 527 | ); 528 | ``` 529 | 530 | You can also compare two different instances of the same type union for equality without needing to access the values. 531 | 532 | ```CSharp 533 | Pet pet1 = new Cat("Mr Fluffy"); 534 | Pet pet2 = new Dog("Spot"); 535 | if (pet1 == pet2) { ... } 536 | ``` 537 | 538 | ### Declaring a Tag Union 539 | 540 | You declare a tag union by declaring a partial struct with a `TagUnion` attribute, 541 | and a partial factory method for each case. 542 | 543 | ```CSharp 544 | [TagUnion] 545 | public partial struct Pet 546 | { 547 | public static partial Pet Cat(string name, int toys); 548 | public static partial Pet Dog(string name, bool friendly); 549 | public static partial Pet Bird(string name, string[] thingsItSays); 550 | } 551 | ``` 552 | 553 | You use it similarly to how type unions are used, without some of the additional API like coercion operators, 554 | generic factories and accessors. 555 | . 556 | 557 | ```CSharp 558 | Pet pet = Pet.Cat("Mr Fluffy"); 559 | ... 560 | switch (pet.Kind) 561 | { 562 | case Pet.Case.Cat: 563 | Console.WriteLine($"Cat's name is {pet.CatValues.name}"); 564 | break; 565 | case Pet.Case.Dog: 566 | Console.WriteLine($"Dog's name is {pet.DogValues.name}"); 567 | break; 568 | case Pet.Case.Bird: 569 | Console.WriteLine($"Bird's name is {pet.BirdValues.name}"); 570 | break; 571 | } 572 | ``` 573 | In addition, some of the generated accessor properties have different names than for type unions. 574 | 575 | - If the case has multiple values, the property is named [case]Values and a tuple of all the values are returned. 576 | - If the case has only one value, then the property is named [case]Value and just that one value is returned. 577 | - If the case has no values, an Is[case] method is generated instead and returns `bool`. 578 | 579 | *It is possible to customize all the factory and property names.* 580 | 581 | A tag union also has `Match` and `Select` methods, just like the type union. 582 | 583 | ### Customizing the Generation with Attributes 584 | 585 | You can customize the generated union by setting properties in the `TypeUnion` or `TagUnion` attribute. 586 | 587 | | Property | Type | Description | Default | 588 | |:-----------|:-----|:--------------|---------| 589 | | DecomposeStructs | bool | Enables deconstruction of tuple and record structs to improve field resuse across cases. | true | 590 | | DecomposeForeignStructs | bool | Enables deconstruction of tuple and record structs defined outside the compilation unit. Metadata for foreign types may be incomplete. Disable this to allow correct storing of custom record structs that have additional data fields not accessible via deconstruction.| true | 591 | | GenerateEquality | bool | Enables generation of `IEquatable` implementation. | true | 592 | | GenerateMatch | bool | Enables generation of `Select` and `Match` methods. | true | 593 | | GenerateToString | bool | Enables generation of `ToString` override. | true | 594 | | OverlapStructs | bool | Enables overlapping of values that can share the same memory across cases. This incluse primitive structs and structs containing only other overlappable fields. | true | 595 | | OverlapForeignStructs | bool | Enables overlapping of structs defined outside the compilation unit. This is disabled by default because the metadata for foreign structs may be incomplete. Enable this only if you trust the types involved to truly be overlappable. | false | 596 | | ShareSameTypeFields | bool | Enables reuse of fields with the same type across cases. | true | 597 | | ShareReferenceFields | bool | Enables reuse of reference type fields regardless of type across cases. | true | 598 | | TagTypeName | string | The name of the enum type generated for the case values. | Case | 599 | | TagPropertyName | string | The name of the property generated to access the case. | Kind | 600 | 601 | ```CSharp 602 | [TypeUnion(GenerateMatch=true, GenerateEquality=true)] 603 | public partial struct Pet 604 | { 605 | public static partial Pet Create(Cat cat); 606 | public static partial Pet Create(Dog dog); 607 | public static partial Pet Create(Bird bird); 608 | } 609 | ``` 610 | 611 | #### You can also customize each case: 612 | 613 | In order to customize settings for individual case you can specify a `Case` attribute on the factory method. 614 | 615 | | Property | Type | Description | Default | 616 | |:---------|:-----|:------------|:--------| 617 | | Name | string | The name of the case | Infered from factory, type or parameter name | 618 | | TagValue | int | The corresponding enum value for the case | Incrementally generated | 619 | | FactoryName | string | The name of the factory if not declared | 620 | | FactoryKind | string | The kind of factory generated if not declared; can be Method, Property or None | Method* | 621 | | AccessorName | string | The name of the accessor property | [Case]Value, [Case]Values or Is[Case] | 622 | | AccessorKind | string | The kind of accessor generated; can be Method, Property or None. | Property | 623 | | Type | Type | The type of the case | Inferred from factory | 624 | 625 | ```CSharp 626 | [TypeUnion] 627 | public partial struct Pet 628 | { 629 | [Case(Name="C", TagValue=1, AccessorName="CatThings")] 630 | public static partial Pet Create(AbcCat cat); 631 | 632 | [Case(Name="D", TagValue=2, AccessorName="DogThings")] 633 | public static partial Pet Create(XyzDog dog); 634 | 635 | [Case(Name="B", TagValue=3, AccessorName="BirdThings")] 636 | public static partial Pet Create(AcmeBird bird); 637 | } 638 | ``` 639 | 640 | #### Case Names 641 | 642 | The case name is used in the tag enum class and as part of the inferred accessor name. 643 | 644 | The generator will try its best to identify the most appropriate name for each case from the factory declaration. 645 | To infer the case name from the factory, the generator follows these steps: 646 | 647 | For tag unions: 648 | - If all the factory names are different, the name for each case is the factory name. If all factory names have a common prefix, the prefix is removed. 649 | - Otherwise, the case name is "Case" + n. 650 | 651 | For type unions: 652 | - If all the factory names are different, the name for each case is the factory name. If all factory names have a common prefix, the prefix is removed. 653 | - If all parameter names are different, then each case name is inferred from the parameter name. 654 | - If all the case type names are different, then the case name is the name of the case type. 655 | - Otherwise, then the case name is "Type" + n. 656 | 657 | In the following example, the generator infers the case names Cat, Dog, Bird from the factory names. 658 | The common prefix of 'Create' is removed. 659 | ```CSharp 660 | [TagUnion] 661 | public partial struct Pet 662 | { 663 | public static partial Pet CreateCat(string name); 664 | public static partial Pet CreateDog(string name); 665 | public static partial Pet CreateBird(string name); 666 | } 667 | ``` 668 | 669 | In this example, the generator infers the case names from the type names 670 | because the factory names and the parameter names are not all different from each other, 671 | but the type names are. 672 | ```CSharp 673 | [TypeUnion] 674 | public partial struct Pet 675 | { 676 | public static partial Pet Create(Cat value); 677 | public static partial Pet Create(Dog value); 678 | public static partial Pet Create(Bird value); 679 | } 680 | ``` 681 | 682 | In this example, the generator infers the case names as Cats, Dogs, Birds from the parameter names because they are all different, 683 | but not all of the types have good unique names. 684 | ```CSharp 685 | [TypeUnion] 686 | public partial struct Pets 687 | { 688 | public static partial Pets Create(IEnumerable cats); 689 | public static partial Pets Create(IEnumerable dogs); 690 | public static partial Pets Create(IEnumerable birds); 691 | } 692 | ``` 693 | 694 | In the final example, the generator gives up and infers the names Type1, Type2, Type3, because neither the factory names, parameter names or type names are all different. 695 | ```CSharp 696 | [TypeUnion] 697 | public partial struct Pets 698 | { 699 | public static partial Pets Create(IEnumerable value); 700 | public static partial Pets Create(IEnumerable value); 701 | public static partial Pets Create(IEnumerable value); 702 | } 703 | ``` 704 | 705 | 706 | 707 | 708 | ### Declaring a Case Factory as a Property 709 | 710 | If a tag case has no case values you can omit declaring the factory property and instead place a `Case` attribute for it on the union declaration. 711 | When you do so, the factory is generated as a property instead of a method. 712 | 713 | ```CSharp 714 | [TagUnion] 715 | [Case(Name="Unknown")] 716 | public partial struct Pet 717 | { 718 | public static partial Pet Cat(string name, int toys); 719 | public static partial Pet Dog(string name, bool friendly); 720 | public static partial Pet Bird(string name, string[] thingsItSays); 721 | } 722 | ``` 723 | 724 | The new `Unknown` state is now defined as a property. 725 | 726 | ```CSharp 727 | Pet pet = Pet.Unknown; 728 | ``` 729 | 730 | You can change how the factory is generated by default with the `FactoryKind` property in the `Case` attribute. 731 | 732 | Likewise, if the type used in a type union case is a singleton (a type that only ever has one instance), 733 | you can have the factory for that case be a property by placing a `Case` attribute on the type union declaration 734 | instead of specifying the factory, and setting the `FactoryKind`. 735 | 736 | Normally, if the type used in a type case is a singleton, its factory is still generated by default with a single parameter, 737 | but with `FactoryKind="Property"`, it will be generated as a property. 738 | 739 | ```CSharp 740 | // Unknown is a singleton 741 | public class Unknown 742 | { 743 | private Union(){} 744 | public static readonly Unknown Singleton = new Unknown(); 745 | } 746 | 747 | [TypeUnion] 748 | [Case(Type=typeof(Unknown), FactoryKind="Property")] 749 | [Case(Type=typeof(Cat))] 750 | [Case(Type=typeof(Dog))] 751 | [Case(Type=typeof(Bird))] 752 | public partial struct Pet 753 | { 754 | } 755 | ``` 756 | Note that even though the factory is a property, you can still assign the singleton value to the union, 757 | since the implicit coercion operator is still being generated. 758 | 759 | ```CSharp 760 | Pet pet = Pet.Unknown; // factory 761 | Pet pet2 = Unknown.Singleton; // assignment 762 | ``` 763 | 764 | *For a type to be recognized as a singleton, it must have only private constructors, and declare only one member, a static readonly field that returns an instance of the value.* 765 | 766 | ### Declaring a Case without an Accessor. 767 | 768 | If a case of a type union is a singleton type or a case of a tag union has no values, you can omit the generation of the 769 | accessor property by setting `AccessorKind` in the `Case` attribute. 770 | 771 | For example, normally a tag union will generate `Is` properties for cases without values. 772 | When you set the `AccessorKind` to `None`, the `Is` property is not generated. 773 | 774 | ```CSharp 775 | [TagUnion] 776 | [Case(Type=typeof(Cat, AccessorKind="None")] 777 | public partial struct Pet 778 | { 779 | [Case(AccessorKind="None")] 780 | public static partial Pet Dog(); 781 | 782 | public static partial Pet Bird(string name, int numberOfThingsItSays); 783 | } 784 | ``` 785 | 786 | Now only the bird case as a value accessor, since its the only one with values. 787 | 788 | ### Assigning Specific Tag Values to Cases. 789 | 790 | You may want to avoid versioning problems by future-proofing your union types by assigning them specific tag values for each case. 791 | This is the same as you might do with an enum. 792 | 793 | By giving each case a unique value, you can keep some binary compatability with past versions of the union type, 794 | when new cases are added. 795 | 796 | Of course, this won't solve the real problem of code not handling the new cases, 797 | but at least it keeps the code that does handle the old cases from breaking. 798 | 799 | ```CSharp 800 | [TagUnion] 801 | public partial struct Pet 802 | { 803 | [Case(TagValue=1)] 804 | public static partial Pet Cat(string name, int toys); 805 | [Case(TagValue=2)] 806 | public static partial Pet Dog(string name, bool friendly); 807 | [Case(TagValue=3)] 808 | public static partial Pet Bird(string name, string[] thingsItSays); 809 | } 810 | ``` 811 | 812 | *You can do this for both type and tag unions.* 813 | 814 | ### Declaring a Default Case for a Type or Tag Union 815 | 816 | If your custom type or tag union is a struct you may encounter problems that occur when the union is in its default state, 817 | because it is not yet assigned or it was assigned `default`. 818 | 819 | You can choose one of your cases to be the default case for this situtation by assigning a `TagValue` of `0`, 820 | since the tag value will otherwise be zero when it is not yet assigned one of the known cases. 821 | >You should only do this only for a value-less tag case or a singleton type case. 822 | 823 | *By default, all tag values are assigned postive, non-zero values to avoid being associated with the default state.* 824 | 825 | ### Generating Union Types without the Toolkit 826 | 827 | It is possible to use the generator and not use the toolkit. 828 | 829 | Since you wont have access to the `TypeUnion` and `TagUnion` attriutes, 830 | you can place the `@TypeUnion` or `@TagUnion` annotation inside a comment above the partial type declaration. 831 | This is enough to trigger the source generator, but you will only get a type generated with the defaults. 832 | 833 | ```CSharp 834 | // @TypeUnion 835 | public partial struct Pet 836 | { 837 | public static partial Pet Create(Cat cat); 838 | public static partial Pet Create(Dog dog); 839 | public static partial Pet Create(Bird bird); 840 | } 841 | ``` 842 | 843 | The ability to customize these may be added in the future. 844 | 845 | ### Unions with Spans 846 | 847 | Custom unions may refer to values of type `Span` or `ReadOnlySpan` or any ref struct type, 848 | as long as the declared union is itself a ref struct type. 849 | 850 | ```CSharp 851 | [TypeUnion] 852 | public ref partial struct RefText 853 | { 854 | public static partial RefText Create(ReadOnlySpan bytes); 855 | public static partial RefText Create(ReadOnlySpan chars); 856 | } 857 | ``` 858 | 859 | These unions work with some limitations: 860 | - Type Unions will not implement `ITypeUnion` or generate `TryCreate` or `TryGet` methods. 861 | - Equality methods will not be generated, since the ref types cannot implement `IEquatable` or be boxed. 862 | - ToString will not be generated 863 | - Storage of ref structs is not memory efficient, they will not overlap or decompose, but they may share same type fields across cases. 864 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS1591: Missing XML comment for publicly visible type or member 4 | dotnet_diagnostic.CS1591.severity = suggestion 5 | -------------------------------------------------------------------------------- /src/Generators/Generator.cs: -------------------------------------------------------------------------------- 1 | // <#+ 2 | #if !T4 3 | namespace UnionTypes.Generators 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | #endif 10 | 11 | #nullable enable 12 | 13 | public abstract class Generator 14 | { 15 | private StringBuilder _builder = new StringBuilder(); 16 | private string _lineStartIndentation = ""; 17 | private const string _indent = " "; 18 | private bool _isLineStart = true; 19 | 20 | public string GeneratedText => _builder.ToString(); 21 | 22 | protected void Write(string text) 23 | { 24 | if (_isLineStart) 25 | { 26 | _builder.Append(_lineStartIndentation); 27 | _isLineStart = false; 28 | } 29 | 30 | _builder.Append(text); 31 | } 32 | 33 | protected void WriteLine(string? text = null) 34 | { 35 | if (text != null) 36 | Write(text); 37 | _builder.AppendLine(); 38 | _isLineStart = true; 39 | } 40 | 41 | protected void WriteLineNested(string text) 42 | { 43 | WriteNested(() => WriteLine(text)); 44 | } 45 | 46 | protected void WriteNested(Action action) 47 | { 48 | var oldLineStartIndentation = _lineStartIndentation; 49 | _lineStartIndentation = _lineStartIndentation + _indent; 50 | action(); 51 | _lineStartIndentation = oldLineStartIndentation; 52 | } 53 | 54 | protected void WriteNested(string open, string close, Action action) 55 | { 56 | if (!_isLineStart) 57 | WriteLine(); 58 | WriteLine(open); 59 | WriteNested(action); 60 | WriteLine(close); 61 | } 62 | 63 | protected void WriteBraceNested(Action action) 64 | { 65 | WriteNested("{", "}", action); 66 | } 67 | 68 | private List _blocks = default!; 69 | 70 | protected void WriteLineSeparatedBlocks(Action action) 71 | { 72 | var oldBuilder = _builder; 73 | var oldBlocks = _blocks; 74 | _builder = new StringBuilder(); 75 | _blocks = new List(); 76 | 77 | action(); 78 | 79 | _builder = oldBuilder; 80 | 81 | if (_blocks.Count > 0) 82 | { 83 | _builder.Append(string.Join(Environment.NewLine, _blocks)); 84 | } 85 | 86 | _blocks = oldBlocks; 87 | } 88 | 89 | protected void WriteBlock(Action action) 90 | { 91 | // any writes outside of WriteBlock is treated as a separate block 92 | if (_builder.Length > 0) 93 | { 94 | _blocks.Add(_builder.ToString()); 95 | _builder.Clear(); 96 | } 97 | 98 | action(); 99 | 100 | if (_builder.Length > 0) 101 | { 102 | _blocks.Add(_builder.ToString()); 103 | _builder.Clear(); 104 | } 105 | } 106 | 107 | /// 108 | /// Writes a blank line between each action 109 | /// 110 | protected void WriteLineSeparated(params Action[] actions) 111 | { 112 | WriteLineSeparatedBlocks(() => 113 | { 114 | foreach (var action in actions) 115 | { 116 | WriteBlock(action); 117 | } 118 | }); 119 | } 120 | 121 | private bool _firstListElement = false; 122 | protected void WriteCommaList(Action action) 123 | { 124 | var oldFirstListElement = _firstListElement; 125 | _firstListElement = true; 126 | action(); 127 | _firstListElement = oldFirstListElement; 128 | } 129 | 130 | protected void WriteCommaListElement(Action action) 131 | { 132 | if (!_firstListElement) 133 | Write(", "); 134 | action(); 135 | _firstListElement = false; 136 | } 137 | 138 | 139 | internal static string LowerName(string name) 140 | { 141 | if (!char.IsLower(name[0])) 142 | return char.ToLower(name[0]) + name.Substring(1); 143 | return name; 144 | } 145 | 146 | internal static string UpperName(string name) 147 | { 148 | if (!char.IsUpper(name[0])) 149 | return char.ToUpper(name[0]) + name.Substring(1); 150 | return name; 151 | } 152 | } 153 | 154 | #if !T4 155 | } 156 | #endif 157 | // #> -------------------------------------------------------------------------------- /src/Generators/Generators.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 11 6 | enable 7 | GeneratorsX 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Generators/OneOfGenerator.cs: -------------------------------------------------------------------------------- 1 | // <#+ 2 | #if !T4 3 | namespace UnionTypes.Generators 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | #endif 10 | 11 | #nullable enable 12 | 13 | public class OneOfGenerator : Generator 14 | { 15 | private readonly int _maxTypeArgs; 16 | private readonly string _namespaceName; 17 | 18 | private OneOfGenerator(int maxTypeArgs, string namespaceName) 19 | { 20 | _maxTypeArgs = maxTypeArgs; 21 | _namespaceName = namespaceName; 22 | } 23 | 24 | public static string Generate(int maxTypeArgs, string namespaceName) 25 | { 26 | var generator = new OneOfGenerator(maxTypeArgs, namespaceName); 27 | generator.WriteFile(); 28 | return generator.GeneratedText; 29 | } 30 | 31 | private void WriteFile() 32 | { 33 | WriteLine("using System;"); 34 | WriteLine("using System.Collections.Generic;"); 35 | WriteLine("using System.Diagnostics.CodeAnalysis;"); 36 | WriteLine("#nullable enable"); 37 | WriteLine(); 38 | if (!string.IsNullOrEmpty(_namespaceName)) 39 | { 40 | WriteLine($"namespace {_namespaceName}"); 41 | WriteBraceNested(() => 42 | { 43 | WriteOneOfTypes(); 44 | }); 45 | } 46 | else 47 | { 48 | WriteOneOfTypes(); 49 | } 50 | } 51 | 52 | private void WriteOneOfTypes() 53 | { 54 | for (int nTypeArgs = 2; nTypeArgs <= _maxTypeArgs; nTypeArgs++) 55 | { 56 | WriteOneOfType(nTypeArgs); 57 | if (nTypeArgs < _maxTypeArgs) 58 | WriteLine(); 59 | } 60 | } 61 | 62 | private int _nTypeArgs = 0; 63 | private string _typeArgList = ""; 64 | private string _oneOfType = ""; 65 | 66 | private void WriteOneOfType(int nTypeArgs) 67 | { 68 | _nTypeArgs = nTypeArgs; 69 | _typeArgList = string.Join(", ", Enumerable.Range(1, nTypeArgs).Select(n => $"T{n}")); 70 | _oneOfType = $"OneOf<{_typeArgList}>"; 71 | 72 | WriteLine($"public struct {_oneOfType}"); 73 | WriteLineNested($": IOneOf, IClosedTypeUnion<{_oneOfType}>, IEquatable<{_oneOfType}>"); 74 | WriteBraceNested(() => 75 | { 76 | WriteLineSeparatedBlocks(() => 77 | { 78 | WriteBlock(() => 79 | { 80 | WriteLine("/// The type case for the union's value; 1 == T1, 2 == T2, etc."); 81 | WriteLine("public int Kind { get; }"); 82 | }); 83 | 84 | WriteBlock(() => 85 | { 86 | WriteLine("/// The underlying value of the union."); 87 | WriteLine("public object Value { get; }"); 88 | }); 89 | 90 | WriteBlock(() => 91 | { 92 | WriteLine($"private OneOf(int kind, object value) {{ this.Kind = kind; this.Value = value; }}"); 93 | }); 94 | 95 | for (int i = 1; i <= _nTypeArgs; i++) 96 | { 97 | WriteBlock(() => 98 | { 99 | WriteLine($"public static {_oneOfType} Create(T{i} value)"); 100 | WriteBraceNested(() => 101 | { 102 | WriteLine($"if (value is ITypeUnion u && u.TryGet(out object obj)) return new {_oneOfType}({i}, obj!);"); 103 | WriteLine($"return new {_oneOfType}({i}, value!);"); 104 | }); 105 | }); 106 | } 107 | 108 | WriteBlock(() => 109 | { 110 | WriteLine($"public static bool TryCreate(TValue value, [NotNullWhen(true)] out {_oneOfType} union)"); 111 | WriteBraceNested(() => 112 | { 113 | WriteLine("if (value != null)"); 114 | WriteBraceNested(() => 115 | { 116 | WriteLine("switch (value)"); 117 | WriteBraceNested(() => 118 | { 119 | for (int i = 1; i <= _nTypeArgs; i++) 120 | { 121 | WriteLine($"case T{i} v{i}: union = Create(v{i}); return true;"); 122 | } 123 | }); 124 | 125 | WriteLine("return TypeUnion.TryCreateFromUnion(value, out union);"); 126 | }); 127 | 128 | WriteLine("union = default!;"); 129 | WriteLine("return false;"); 130 | }); 131 | }); 132 | 133 | WriteBlock(() => 134 | { 135 | WriteLine($"public static {_oneOfType} Create(TValue value) => TryCreate(value, out var union) ? union : throw new InvalidCastException(\"Invalid type for union.\");"); 136 | }); 137 | 138 | for (int i = 1; i <= _nTypeArgs; i++) 139 | { 140 | WriteBlock(() => 141 | { 142 | WriteLine($"/// The union's value as type ."); 143 | WriteLine($"public T{i} Type{i}Value => (this.Value is T{i} value || this.TryGet(out value)) ? value : default!;"); 144 | }); 145 | } 146 | 147 | WriteBlock(() => 148 | { 149 | WriteLine("public Type Type => this.Value?.GetType() ?? typeof(object);"); 150 | }); 151 | 152 | WriteBlock(() => 153 | { 154 | var typeList = string.Join(", ", Enumerable.Range(1, nTypeArgs).Select(n => $"typeof(T{n})")); 155 | WriteLine($"static IReadOnlyList IClosedTypeUnion<{_oneOfType}>.Types {{ get; }} ="); 156 | WriteLineNested($"new [] {{ {typeList} }};"); 157 | }); 158 | 159 | WriteBlock(() => 160 | { 161 | WriteLine($"public bool TryGet([NotNullWhen(true)] out TValue value)"); 162 | WriteBraceNested(() => 163 | { 164 | WriteLine("if (this.Value is TValue tval) { value = tval; return true; }"); 165 | WriteLine("return TypeUnion.TryCreate(this.Value, out value);"); 166 | }); 167 | }); 168 | 169 | WriteBlock(() => 170 | { 171 | WriteLine("/// Returns the ToString() result of the value held by the union."); 172 | WriteLine("public override string ToString() => this.Value?.ToString() ?? \"\";"); 173 | }); 174 | 175 | WriteBlock(() => 176 | { 177 | for (int i = 1; i <= _nTypeArgs; i++) 178 | { 179 | WriteLine($"public static implicit operator {_oneOfType}(T{i} value) => Create(value);"); 180 | } 181 | for (int i = 1; i <= _nTypeArgs; i++) 182 | { 183 | WriteLine($"public static explicit operator T{i}({_oneOfType} union) => union.Kind == {i} ? union.Type{i}Value : throw new InvalidCastException();"); 184 | } 185 | }); 186 | 187 | // IEquality 188 | WriteBlock(() => 189 | { 190 | WriteLine($"public bool Equals({_oneOfType} other) => object.Equals(this.Value, other.Value);"); 191 | WriteLine($"public bool Equals(TValue value) => (value is {_oneOfType} other || TryCreate(value, out other)) && Equals(other);"); 192 | WriteLine($"public override bool Equals(object? obj) => Equals(obj);"); 193 | WriteLine($"public override int GetHashCode() => this.Value?.GetHashCode() ?? 0;"); 194 | WriteLine($"public static bool operator ==({_oneOfType} a, {_oneOfType} b) => a.Equals(b);"); 195 | WriteLine($"public static bool operator !=({_oneOfType} a, {_oneOfType} b) => !a.Equals(b);"); 196 | }); 197 | 198 | // select 199 | WriteBlock(() => 200 | { 201 | var parameters = Enumerable.Range(1, _nTypeArgs).Select(i => $"Func whenType{i}").ToList(); 202 | parameters.Add("Func? whenUndefined = null"); 203 | 204 | WriteLine($"public TResult Select({string.Join(", ", parameters)})"); 205 | WriteBraceNested( 206 | () => 207 | { 208 | WriteLine("switch (this.Kind)"); 209 | WriteBraceNested( 210 | () => 211 | { 212 | for (int i = 1; i <= _nTypeArgs; i++) 213 | { 214 | WriteLine($"case {i}: return whenType{i}(Type{i}Value);"); 215 | } 216 | 217 | WriteLine("""default: return whenUndefined != null ? whenUndefined() : throw new InvalidOperationException("Undefined union state.");"""); 218 | }); 219 | }); 220 | 221 | }); 222 | 223 | // match 224 | WriteBlock(() => 225 | { 226 | var parameters = Enumerable.Range(1, _nTypeArgs).Select(i => $"Action whenType{i}").ToList(); 227 | parameters.Add("Action? whenUndefined = null"); 228 | 229 | WriteLine($"public void Match({string.Join(", ", parameters)})"); 230 | WriteBraceNested( 231 | () => 232 | { 233 | WriteLine("switch (this.Kind)"); 234 | WriteBraceNested( 235 | () => 236 | { 237 | for (int i = 1; i <= _nTypeArgs; i++) 238 | { 239 | WriteLine($"case {i}: whenType{i}(Type{i}Value); break;"); 240 | } 241 | WriteLine("""default: if (whenUndefined != null) whenUndefined(); else throw new InvalidOperationException("Invalid union state."); break;"""); 242 | }); 243 | }); 244 | }); 245 | 246 | }); 247 | }); 248 | } 249 | } 250 | 251 | #if !T4 252 | } 253 | #endif 254 | // #> -------------------------------------------------------------------------------- /src/Scratch/MyUnion.cs: -------------------------------------------------------------------------------- 1 | Console.WriteLine("Temporary scratch pad for debugging generated union types"); -------------------------------------------------------------------------------- /src/Scratch/MyUnion_generated.cs: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/Scratch/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /src/Scratch/Scratch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/UnionGeneratorTests/MSTestSettings.cs: -------------------------------------------------------------------------------- 1 | [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] 2 | -------------------------------------------------------------------------------- /src/UnionGeneratorTests/UnionGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using UnionTypes; 2 | using UnionTypes.Generators; 3 | 4 | namespace UnionTests 5 | { 6 | [TestClass] 7 | public class UnionGeneratorTests 8 | { 9 | [TestMethod] 10 | public void TestTagUnion_Result() 11 | { 12 | TestGenerate( 13 | new Union( 14 | UnionKind.TypeUnion, 15 | "Result", 16 | "Result", 17 | "public", 18 | new[] 19 | { 20 | new UnionCase( 21 | name: "Success", 22 | new UnionValueType("TValue", TypeKind.TypeParameter_Unconstrained), 23 | tagValue: 1, 24 | factoryName:"Success", 25 | factoryParameters: new [] { new UnionCaseValue("value", new UnionValueType("TValue", TypeKind.TypeParameter_Unconstrained)) }, 26 | accessorName: "SuccessValue"), 27 | new UnionCase( 28 | name: "Failure", 29 | new UnionValueType("TError", TypeKind.TypeParameter_Unconstrained), 30 | tagValue: 2, 31 | factoryName: "Failure", 32 | factoryParameters: new [] { new UnionCaseValue("error", new UnionValueType("TError", TypeKind.TypeParameter_Unconstrained)) }, 33 | accessorName: "FailureValue") 34 | }, 35 | UnionOptions.Default 36 | .WithUseToolkit(true) 37 | .WithGenerateMatch(true) 38 | .WithGenerateEquality(true) 39 | .WithGenerateToString(true) 40 | ), 41 | namespaceName: "UnionTypes.Toolkit" 42 | ); 43 | } 44 | 45 | [TestMethod] 46 | public void TestTagUnion_Option() 47 | { 48 | TestGenerate( 49 | new Union( 50 | UnionKind.TypeUnion, 51 | "Option", 52 | "Option", 53 | "public", 54 | new[] 55 | { 56 | new UnionCase( 57 | name: "Some", 58 | type: null, 59 | tagValue: 1, 60 | factoryName: "Some", 61 | factoryParameters: new [] { new UnionCaseValue("value", new UnionValueType("TValue", TypeKind.TypeParameter_Unconstrained)) }, 62 | accessorName: "Value"), 63 | new UnionCase( 64 | name: "None", 65 | type: new UnionValueType("UnionTypes.Toolkit.None", TypeKind.Class, "Singleton"), 66 | tagValue: 0, 67 | factoryName:"None", 68 | factoryParameters: null, 69 | accessorKind: CaseAccessorKind.None) 70 | }, 71 | UnionOptions.Default 72 | .WithUseToolkit(true) 73 | .WithGenerateMatch(true) 74 | .WithGenerateEquality(true) 75 | .WithGenerateToString(true) 76 | ), 77 | namespaceName: "UnionTypes.Toolkit" 78 | ); 79 | } 80 | 81 | 82 | [TestMethod] 83 | public void TestTypeUnion_CatOrDog() 84 | { 85 | var dogType = new UnionValueType("Dog", TypeKind.DecomposableLocalRecordStruct); 86 | var catType = new UnionValueType("Cat", TypeKind.DecomposableLocalRecordStruct); 87 | 88 | TestGenerate( 89 | new Union( 90 | UnionKind.TypeUnion, 91 | name: "DogOrCat", 92 | typeName: "DogOrCat", 93 | modifiers: "public", 94 | [ 95 | new UnionCase( 96 | name: "Dog", 97 | type: dogType, 98 | tagValue: -1, 99 | factoryName:"CreateDog", 100 | factoryParameters: [ 101 | new UnionCaseValue("dog", dogType, [new UnionCaseValue("name", UnionValueType.String)]) 102 | ]), 103 | new UnionCase( 104 | name: "Cat", 105 | type: catType, 106 | tagValue: -1, 107 | factoryName: "CreateCat", 108 | factoryParameters: [ 109 | new UnionCaseValue("cat", catType, [new UnionCaseValue("name", UnionValueType.String)]) 110 | ]), 111 | ], 112 | UnionOptions.Default.WithShareReferenceFields(false) 113 | ), 114 | namespaceName: "TestUnions", 115 | usings: ["System", "System.Collections.Generic", "UnionTypes.Toolkit"] 116 | ); 117 | } 118 | 119 | private void TestGenerate( 120 | Union union, 121 | string? namespaceName = "", 122 | string[]? usings = null 123 | ) 124 | { 125 | var generator = new UnionGenerator( 126 | namespaceName, 127 | usings 128 | ); 129 | 130 | var actualText = generator.GenerateFile(union); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /src/UnionGeneratorTests/UnionGeneratorTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator.Package/ReadMe.Nuget.md: -------------------------------------------------------------------------------- 1 | # UnionTypes.Toolkit.Generator 2 | 3 | A C# source generator for generating union types (discriminated unions). 4 | 5 | [Learn about using the generator in your project here.](https://github.com/mattwar/UnionTypes.Toolkit) 6 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator.Package/UnionSourceGenerator.Package.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | true 7 | true 8 | 9 | 10 | 11 | UnionTypes.Toolkit.Generator 12 | 13 | Matt Warren 14 | false 15 | A C# source generator for generating custom union types. 16 | 17 | Copyright 18 | UnionTypes.Toolkit.Generator, analyzers 19 | true 20 | true 21 | $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput 22 | UnionTypes.Toolkit.Generator.Package 23 | https://github.com/mattwar/UnionTypes.Toolkit 24 | https://github.com/mattwar/UnionTypes.Toolkit 25 | ReadMe.Nuget.md 26 | LICENSE 27 | 28 | 29 | 30 | 31 | True 32 | \ 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | \ 47 | True 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator.Package/build_nuget.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%1"=="" goto usage 3 | dotnet build -c:release -p:version=%1 -p:packageversion=%1 4 | goto done 5 | 6 | :usage 7 | echo usage: build_nuget build_version 8 | 9 | :done 10 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator.Package/erase_installed_nuget.cmd: -------------------------------------------------------------------------------- 1 | rd /S %userprofile%\.nuget\packages\uniontypes.toolkit.generator -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator.Package/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not install analyzers via install.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | if (Test-Path $analyzersPath) 17 | { 18 | # Install the language agnostic analyzers. 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Install language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 55 | } 56 | } 57 | } 58 | } 59 | # SIG # Begin signature block 60 | # MIInugYJKoZIhvcNAQcCoIInqzCCJ6cCAQExDzANBglghkgBZQMEAgEFADB5Bgor 61 | # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG 62 | # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s 63 | # FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDYEwggX/MIID56ADAgECAhMzAAACUosz 64 | # qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD 65 | # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy 66 | # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p 67 | # bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw 68 | # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u 69 | # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy 70 | # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 71 | # AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I 72 | # sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O 73 | # L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA 74 | # v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o 75 | # RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 76 | # q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE 77 | # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw 78 | # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 79 | # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu 80 | # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu 81 | # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w 82 | # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 83 | # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx 84 | # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 85 | # uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp 86 | # kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 87 | # l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u 88 | # TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 89 | # o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti 90 | # yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z 91 | # 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf 92 | # 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK 93 | # WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW 94 | # esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F 95 | # 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS 96 | # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK 97 | # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 98 | # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 99 | # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla 100 | # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS 101 | # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT 102 | # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB 103 | # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG 104 | # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S 105 | # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz 106 | # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 107 | # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u 108 | # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 109 | # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl 110 | # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP 111 | # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB 112 | # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF 113 | # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM 114 | # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ 115 | # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud 116 | # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO 117 | # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 118 | # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y 119 | # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p 120 | # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y 121 | # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB 122 | # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw 123 | # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA 124 | # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY 125 | # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj 126 | # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd 127 | # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ 128 | # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf 129 | # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ 130 | # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j 131 | # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B 132 | # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 133 | # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 134 | # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I 135 | # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZjzCCGYsCAQEwgZUwfjELMAkG 136 | # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 137 | # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z 138 | # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN 139 | # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor 140 | # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgRjg7DcI6 141 | # uhYfXWwAQ6hK0mPW7iyr2tzHR0DHSDJkscIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS 142 | # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN 143 | # BgkqhkiG9w0BAQEFAASCAQB3ERGpqvGnJrsyU0d9lERK2TJW4/OONhZAFjxrEvEk 144 | # PzdH0Fk0otvagAvjHzJ3q0G8C7gwRbXIyGgiYYIMefheNvgd/UKnubUGEzeG9h0/ 145 | # biX5Ro1mxuHBYvc3vqvWD292jXMg00iRmexDsTny8YgSAAWsTdkE8/W2ooEfbG1T 146 | # QkCg6ds9btpA1D1znVYpEbviCJoAfHLbNBr5nzAadgWjQM8nnb3UTvmLDIs5b1LO 147 | # 3lm9w485IBFRnfrj6QinVsCbSD7PU/N1hPY7rKfM9ScZC6QT6kjyuVVa1Ft+VYLH 148 | # qlV9hE6B4CGeB8qkko4x+MKovgbdpCgYz3eePWCakZywoYIXGTCCFxUGCisGAQQB 149 | # gjcDAwExghcFMIIXAQYJKoZIhvcNAQcCoIIW8jCCFu4CAQMxDzANBglghkgBZQME 150 | # AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB 151 | # MDEwDQYJYIZIAWUDBAIBBQAEIC58WTh4Q8r6c2kVXmD8xoHEhya2jc6YZ43KUAIy 152 | # flB4AgZh/WKJ50gYEzIwMjIwMjExMTkwMzQwLjE1M1owBIACAfSggdikgdUwgdIx 153 | # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt 154 | # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p 155 | # Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh 156 | # bGVzIFRTUyBFU046M0JENC00QjgwLTY5QzMxJTAjBgNVBAMTHE1pY3Jvc29mdCBU 157 | # aW1lLVN0YW1wIFNlcnZpY2WgghFoMIIHFDCCBPygAwIBAgITMwAAAYm0v4YwhBxL 158 | # jwABAAABiTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK 159 | # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 160 | # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg 161 | # MjAxMDAeFw0yMTEwMjgxOTI3NDFaFw0yMzAxMjYxOTI3NDFaMIHSMQswCQYDVQQG 162 | # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG 163 | # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg 164 | # SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg 165 | # RVNOOjNCRDQtNEI4MC02OUMzMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt 166 | # cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvQZXxZFm 167 | # a6plmuOyvNpV8xONOwcYolZG/BjyZWGSk5JOGaLyrKId5VxVHWHlsmJE4Svnzsdp 168 | # sKmVx8otONveIUFvSceEZp8VXmu5m1fu8L7c+3lwXcibjccqtEvtQslokQVx0r+L 169 | # 54abrNDarwFG73IaRidIS1i9c+unJ8oYyhDRLrCysFAVxyQhPNZkWK7Z8/VGukaK 170 | # LAWHXCh/+R53h42gFL+9/mAALxzCXXuofi8f/XKCm7xNwVc1hONCCz6oq94AufzV 171 | # NkkIW4brUQgYpCcJm9U0XNmQvtropYDn9UtY8YQ0NKenXPtdgLHdQ8Nnv3igErKL 172 | # rWI0a5n5jjdKfwk+8mvakqdZmlOseeOS1XspQNJAK1uZllAITcnQZOcO5ofjOQ33 173 | # ujWckAXdz+/x3o7l4AU/TSOMzGZMwhUdtVwC3dSbItpSVFgnjM2COEJ9zgCadvOi 174 | # rGDLN471jZI2jClkjsJTdgPk343TQA4JFvds/unZq0uLr+niZ3X44OBx2x+gVlln 175 | # 2c4UbZXNueA4yS1TJGbbJFIILAmTUA9Auj5eISGTbNiyWx79HnCOTar39QEKozm4 176 | # LnTmDXy0/KI/H/nYZGKuTHfckP28wQS06rD+fDS5xLwcRMCW92DkHXmtbhGyRilB 177 | # OL5LxZelQfxt54wl4WUC0AdAEolPekODwO8CAwEAAaOCATYwggEyMB0GA1UdDgQW 178 | # BBSXbx+zR1p4IIAeguA6rHKkrfl7UDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl 179 | # pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j 180 | # b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx 181 | # MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3 182 | # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh 183 | # bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG 184 | # CCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4ICAQCOtLdpWUI4KwfLLrfaKrLB92Dq 185 | # bAspGWM41TaO4Jl+sHxPo522uu3GKQCjmkRWreHtlfyy9kOk7LWax3k3ke8Gtfet 186 | # fbh7qH0LeV2XOWg39BOnHf6mTcZq7FYSZZch1JDQjc98+Odlow+oWih0Dbt4CV/e 187 | # 19ZcE+1n1zzWkskUEd0f5jPIUis33p+vkY8szduAtCcIcPFUhI8Hb5alPUAPMjGz 188 | # wKb7NIKbnf8j8cP18As5IveckF0oh1cw63RY/vPK62LDYdpi7WnG2ObvngfWVKtw 189 | # iwTI4jHj2cO9q37HDe/PPl216gSpUZh0ap24mKmMDfcKp1N4mEdsxz4oseOrPYeF 190 | # sHHWJFJ6Aivvqn70KTeJpp5r+DxSqbeSy0mxIUOq/lAaUxgNSQVUX26t8r+fciko 191 | # fKv23WHrtRV3t7rVTsB9YzrRaiikmz68K5HWdt9MqULxPQPo+ppZ0LRqkOae466+ 192 | # UKRY0JxWtdrMc5vHlHZfnqjawj/RsM2S6Q6fa9T9CnY1Nz7DYBG3yZJyCPFsrgU0 193 | # 5s9ljqfsSptpFdUh9R4ce+L71SWDLM2x/1MFLLHAMbXsEp8KloEGtaDULnxtfS2t 194 | # YhfuKGqRXoEfDPAMnIdTvQPh3GHQ4SjkkBARHL0MY75alhGTKHWjC2aLVOo8obKI 195 | # Bk8hfnFDUf/EyVw4uTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUw 196 | # DQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n 197 | # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y 198 | # YXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhv 199 | # cml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkG 200 | # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 201 | # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z 202 | # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw 203 | # ggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg 204 | # 4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aO 205 | # RmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41 206 | # JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5 207 | # LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL 208 | # 64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9 209 | # QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj 210 | # 0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqE 211 | # UUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0 212 | # kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435 213 | # UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB 214 | # 3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTE 215 | # mr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwG 216 | # A1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93 217 | # d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNV 218 | # HSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV 219 | # HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo 220 | # 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m 221 | # dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j 222 | # cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv 223 | # c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDAN 224 | # BgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4 225 | # sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th54 226 | # 2DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRX 227 | # ud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBew 228 | # VIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0 229 | # DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+Cljd 230 | # QDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFr 231 | # DZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFh 232 | # bHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7n 233 | # tdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+ 234 | # oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6Fw 235 | # ZvKhggLXMIICQAIBATCCAQChgdikgdUwgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI 236 | # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv 237 | # ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh 238 | # dGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046M0JENC00Qjgw 239 | # LTY5QzMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB 240 | # ATAHBgUrDgMCGgMVACGlCa3ketyeuey7bJNpWkMuiCcQoIGDMIGApH4wfDELMAkG 241 | # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 242 | # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z 243 | # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDlsRtBMCIY 244 | # DzIwMjIwMjEyMDEyODMzWhgPMjAyMjAyMTMwMTI4MzNaMHcwPQYKKwYBBAGEWQoE 245 | # ATEvMC0wCgIFAOWxG0ECAQAwCgIBAAICDbMCAf8wBwIBAAICEW8wCgIFAOWybMEC 246 | # AQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEK 247 | # MAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQCImCpEJ2AlAWBBkDABmkqIh1kM 248 | # LPDyea3b7evhOk+YSwXCzxnBIXuppujFT3tnk7w0p0a5YS9uwqbDM/M6rAUMBAR0 249 | # boHamumEITNF5nVh0rlYyRZQ3WraVD2YPhouUINQavmS8ueYoh6r3HeM9QPBAnNB 250 | # vv7GDrZ637+2Dfe60jGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD 251 | # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy 252 | # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w 253 | # IFBDQSAyMDEwAhMzAAABibS/hjCEHEuPAAEAAAGJMA0GCWCGSAFlAwQCAQUAoIIB 254 | # SjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIL86 255 | # iebNndOm+CAgIp67s6y+HI1wHdhaMPILGf48RtXXMIH6BgsqhkiG9w0BCRACLzGB 256 | # 6jCB5zCB5DCBvQQgZndHMdxQV1VsbpWHOTHqWEycvcRJm7cY69l/UmT8j0UwgZgw 257 | # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE 258 | # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD 259 | # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAYm0v4YwhBxL 260 | # jwABAAABiTAiBCDET+l3keOFFxaIqOZWSSuWNO774Ng/t5pe3p4QXoKcvjANBgkq 261 | # hkiG9w0BAQsFAASCAgADYrNFej7RbihwGcC0jF+cTik+HJog++dPEDXeIyBB+2pw 262 | # 23hC5KaX9H05ZknluIq2oxf2MLpKL+gA+76T3k5PnzPNJFDogUn5eFIIsMRpNF0h 263 | # MtPWoPJWYFK2odvKz1HwsuqHRg6hO//NwORcv4xPeAWEFO5+DOXzZKKp/BVDGe/D 264 | # c++y9/l41qpz/F2c3a1lugdqnZz7ZeoaQ8/JMlwrmMbciqcAytCn9A59EWJ1xYd/ 265 | # DaDhQ5Rd8hkcckuxJksjWf6URmc91cb4Jdatkyupq3dDGwCkjGNd2xetrOpqMLOZ 266 | # quoDONSgc9rGrhkf3xgKKVRhLg9bxd3f2oQ0IsOBg2AC5td1eqp6TILc0gei2E3I 267 | # uEAW1d+KXDnajvQmvQkaFHr5wEocTTLgrDglOPPhEaEumSTJS7jKFzUKHiBU005p 268 | # CgQ1So2WJ2RqFx0ppez1N1AFczOVLFllK3WGPLkDsN1GgT0nFfoqvs1WKkzyb2d2 269 | # /v6PVER9xGky7LCu62dhsJCAFUbxF2dJxaC5ofrl98VaO/z72J9on9BTz+eCtcJ9 270 | # rDIpqktGeL02f6+4zctFCyi2wgm6eh8kKvRlAPmN4/MNt9pWHtEV//xFGzGeDajr 271 | # diRhDoMZwsuon4QwS8b2YcKMoZ6gZ2lZah3960fTTmvBTBNqeBtR94KWCy0C0A== 272 | # SIG # End signature block 273 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator.Package/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | # Uninstall the language agnostic analyzers. 17 | if (Test-Path $analyzersPath) 18 | { 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Uninstall language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | try 55 | { 56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 57 | } 58 | catch 59 | { 60 | 61 | } 62 | } 63 | } 64 | } 65 | } 66 | # SIG # Begin signature block 67 | # MIInugYJKoZIhvcNAQcCoIInqzCCJ6cCAQExDzANBglghkgBZQMEAgEFADB5Bgor 68 | # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG 69 | # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL 70 | # yXrxJhYfmibzcOh8caqC0uZprfczDaCCDYEwggX/MIID56ADAgECAhMzAAACUosz 71 | # qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD 72 | # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy 73 | # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p 74 | # bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw 75 | # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u 76 | # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy 77 | # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 78 | # AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I 79 | # sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O 80 | # L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA 81 | # v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o 82 | # RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 83 | # q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE 84 | # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw 85 | # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 86 | # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu 87 | # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu 88 | # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w 89 | # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 90 | # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx 91 | # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 92 | # uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp 93 | # kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 94 | # l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u 95 | # TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 96 | # o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti 97 | # yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z 98 | # 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf 99 | # 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK 100 | # WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW 101 | # esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F 102 | # 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS 103 | # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK 104 | # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 105 | # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 106 | # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla 107 | # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS 108 | # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT 109 | # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB 110 | # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG 111 | # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S 112 | # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz 113 | # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 114 | # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u 115 | # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 116 | # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl 117 | # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP 118 | # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB 119 | # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF 120 | # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM 121 | # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ 122 | # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud 123 | # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO 124 | # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 125 | # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y 126 | # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p 127 | # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y 128 | # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB 129 | # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw 130 | # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA 131 | # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY 132 | # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj 133 | # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd 134 | # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ 135 | # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf 136 | # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ 137 | # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j 138 | # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B 139 | # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 140 | # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 141 | # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I 142 | # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZjzCCGYsCAQEwgZUwfjELMAkG 143 | # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 144 | # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z 145 | # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN 146 | # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor 147 | # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgF1ypFyzl 148 | # AvvWGVCeXczrfpXmJNm9vpyjcwd4y4ivfqowQgYKKwYBBAGCNwIBDDE0MDKgFIAS 149 | # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN 150 | # BgkqhkiG9w0BAQEFAASCAQAvi2rSDkhC82RJ4uqq/0WbHkOkzq1hrF6HxneBTNj8 151 | # KX+niFtee3CYVfWaSAQ6xvOiLupRX3fsSfhabRQ+Jl8k28voGrTK1OC906OO3tUN 152 | # jdmv1PooWdxJNt2EbzQrap5Ui9KTUv4mJ4c836HAVMBPCJiq5NwmzAHfbgBxCaYq 153 | # +hupIf+gk8vuNB1bltILgNmU/smJt9OuGqSo5TrFajzb+35SqjnCz9JtAtbPNZvA 154 | # X9N37UPhITOecceAQmrHiEPbA7eu6VDp6VPjPfCEO7a+frWa83chEd4qzyou9xu5 155 | # 3gnj7Ro8nFDnGyUe0+0oCaYGXO9fbIMN1HG2IZg5suj5oYIXGTCCFxUGCisGAQQB 156 | # gjcDAwExghcFMIIXAQYJKoZIhvcNAQcCoIIW8jCCFu4CAQMxDzANBglghkgBZQME 157 | # AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB 158 | # MDEwDQYJYIZIAWUDBAIBBQAEIH+XBTHuyyHZnIXrFWIe64WLvHx5GUFMCM6A56T1 159 | # KwBtAgZh/WKJ52UYEzIwMjIwMjExMTkwMzQwLjU0OFowBIACAfSggdikgdUwgdIx 160 | # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt 161 | # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p 162 | # Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh 163 | # bGVzIFRTUyBFU046M0JENC00QjgwLTY5QzMxJTAjBgNVBAMTHE1pY3Jvc29mdCBU 164 | # aW1lLVN0YW1wIFNlcnZpY2WgghFoMIIHFDCCBPygAwIBAgITMwAAAYm0v4YwhBxL 165 | # jwABAAABiTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK 166 | # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 167 | # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg 168 | # MjAxMDAeFw0yMTEwMjgxOTI3NDFaFw0yMzAxMjYxOTI3NDFaMIHSMQswCQYDVQQG 169 | # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG 170 | # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg 171 | # SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg 172 | # RVNOOjNCRDQtNEI4MC02OUMzMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt 173 | # cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvQZXxZFm 174 | # a6plmuOyvNpV8xONOwcYolZG/BjyZWGSk5JOGaLyrKId5VxVHWHlsmJE4Svnzsdp 175 | # sKmVx8otONveIUFvSceEZp8VXmu5m1fu8L7c+3lwXcibjccqtEvtQslokQVx0r+L 176 | # 54abrNDarwFG73IaRidIS1i9c+unJ8oYyhDRLrCysFAVxyQhPNZkWK7Z8/VGukaK 177 | # LAWHXCh/+R53h42gFL+9/mAALxzCXXuofi8f/XKCm7xNwVc1hONCCz6oq94AufzV 178 | # NkkIW4brUQgYpCcJm9U0XNmQvtropYDn9UtY8YQ0NKenXPtdgLHdQ8Nnv3igErKL 179 | # rWI0a5n5jjdKfwk+8mvakqdZmlOseeOS1XspQNJAK1uZllAITcnQZOcO5ofjOQ33 180 | # ujWckAXdz+/x3o7l4AU/TSOMzGZMwhUdtVwC3dSbItpSVFgnjM2COEJ9zgCadvOi 181 | # rGDLN471jZI2jClkjsJTdgPk343TQA4JFvds/unZq0uLr+niZ3X44OBx2x+gVlln 182 | # 2c4UbZXNueA4yS1TJGbbJFIILAmTUA9Auj5eISGTbNiyWx79HnCOTar39QEKozm4 183 | # LnTmDXy0/KI/H/nYZGKuTHfckP28wQS06rD+fDS5xLwcRMCW92DkHXmtbhGyRilB 184 | # OL5LxZelQfxt54wl4WUC0AdAEolPekODwO8CAwEAAaOCATYwggEyMB0GA1UdDgQW 185 | # BBSXbx+zR1p4IIAeguA6rHKkrfl7UDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl 186 | # pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j 187 | # b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx 188 | # MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3 189 | # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh 190 | # bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG 191 | # CCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4ICAQCOtLdpWUI4KwfLLrfaKrLB92Dq 192 | # bAspGWM41TaO4Jl+sHxPo522uu3GKQCjmkRWreHtlfyy9kOk7LWax3k3ke8Gtfet 193 | # fbh7qH0LeV2XOWg39BOnHf6mTcZq7FYSZZch1JDQjc98+Odlow+oWih0Dbt4CV/e 194 | # 19ZcE+1n1zzWkskUEd0f5jPIUis33p+vkY8szduAtCcIcPFUhI8Hb5alPUAPMjGz 195 | # wKb7NIKbnf8j8cP18As5IveckF0oh1cw63RY/vPK62LDYdpi7WnG2ObvngfWVKtw 196 | # iwTI4jHj2cO9q37HDe/PPl216gSpUZh0ap24mKmMDfcKp1N4mEdsxz4oseOrPYeF 197 | # sHHWJFJ6Aivvqn70KTeJpp5r+DxSqbeSy0mxIUOq/lAaUxgNSQVUX26t8r+fciko 198 | # fKv23WHrtRV3t7rVTsB9YzrRaiikmz68K5HWdt9MqULxPQPo+ppZ0LRqkOae466+ 199 | # UKRY0JxWtdrMc5vHlHZfnqjawj/RsM2S6Q6fa9T9CnY1Nz7DYBG3yZJyCPFsrgU0 200 | # 5s9ljqfsSptpFdUh9R4ce+L71SWDLM2x/1MFLLHAMbXsEp8KloEGtaDULnxtfS2t 201 | # YhfuKGqRXoEfDPAMnIdTvQPh3GHQ4SjkkBARHL0MY75alhGTKHWjC2aLVOo8obKI 202 | # Bk8hfnFDUf/EyVw4uTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUw 203 | # DQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n 204 | # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y 205 | # YXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhv 206 | # cml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkG 207 | # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 208 | # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z 209 | # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw 210 | # ggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg 211 | # 4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aO 212 | # RmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41 213 | # JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5 214 | # LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL 215 | # 64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9 216 | # QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj 217 | # 0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqE 218 | # UUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0 219 | # kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435 220 | # UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB 221 | # 3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTE 222 | # mr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwG 223 | # A1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93 224 | # d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNV 225 | # HSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV 226 | # HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo 227 | # 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m 228 | # dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j 229 | # cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv 230 | # c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDAN 231 | # BgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4 232 | # sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th54 233 | # 2DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRX 234 | # ud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBew 235 | # VIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0 236 | # DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+Cljd 237 | # QDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFr 238 | # DZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFh 239 | # bHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7n 240 | # tdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+ 241 | # oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6Fw 242 | # ZvKhggLXMIICQAIBATCCAQChgdikgdUwgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI 243 | # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv 244 | # ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh 245 | # dGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046M0JENC00Qjgw 246 | # LTY5QzMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB 247 | # ATAHBgUrDgMCGgMVACGlCa3ketyeuey7bJNpWkMuiCcQoIGDMIGApH4wfDELMAkG 248 | # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx 249 | # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z 250 | # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDlsRtBMCIY 251 | # DzIwMjIwMjEyMDEyODMzWhgPMjAyMjAyMTMwMTI4MzNaMHcwPQYKKwYBBAGEWQoE 252 | # ATEvMC0wCgIFAOWxG0ECAQAwCgIBAAICDbMCAf8wBwIBAAICEW8wCgIFAOWybMEC 253 | # AQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEK 254 | # MAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQCImCpEJ2AlAWBBkDABmkqIh1kM 255 | # LPDyea3b7evhOk+YSwXCzxnBIXuppujFT3tnk7w0p0a5YS9uwqbDM/M6rAUMBAR0 256 | # boHamumEITNF5nVh0rlYyRZQ3WraVD2YPhouUINQavmS8ueYoh6r3HeM9QPBAnNB 257 | # vv7GDrZ637+2Dfe60jGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD 258 | # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy 259 | # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w 260 | # IFBDQSAyMDEwAhMzAAABibS/hjCEHEuPAAEAAAGJMA0GCWCGSAFlAwQCAQUAoIIB 261 | # SjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIKY2 262 | # Onyhltfi0+oc/UMKaXc0H6Ckw2gGK1/qmjRZNiXnMIH6BgsqhkiG9w0BCRACLzGB 263 | # 6jCB5zCB5DCBvQQgZndHMdxQV1VsbpWHOTHqWEycvcRJm7cY69l/UmT8j0UwgZgw 264 | # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE 265 | # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD 266 | # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAYm0v4YwhBxL 267 | # jwABAAABiTAiBCDET+l3keOFFxaIqOZWSSuWNO774Ng/t5pe3p4QXoKcvjANBgkq 268 | # hkiG9w0BAQsFAASCAgB7AQ0Dv3muHoNAt+cccMfYk23lHgh8LGBitCSFwu0q7ufv 269 | # sXkoaIpwW0U0GikWhQoCH0U38SuzVbafg49FiE6ftkjOtiE03PwPYi1S6NSoDdaV 270 | # kUuvjz3OcuN1IHg3CyLn2dO8xbUlWCUfgoWhI1nax9ch7wT4Sw8RdmGKdYTZoZmq 271 | # vPXFRtDyZdmJDMDbTql/Brye8oEsDMoYKMmEYhY1t9TlusnWfUbxuBnyMqg/FkBy 272 | # QF78WFfT8mygMqUGmINxPGT6daxqmq3nfAC2vOtLT4DplNYMEymfDceJzBhb8VCT 273 | # UHc2CWK0qKT+eqwn30NBkwh//8aNHlXaA9Yq/9k2y+axIGdxFfG+X0stipRRpEXb 274 | # xCFm7FPD5/S4ddBH829yEZLZ4XTwSZ6YS/d3mFzu5rgZl3UhjOJPXx40GQtUiDP4 275 | # XQZ/wW3154X/KtTypv62/Hl+CiMUrsO7MXtgwClfbJ3osg+zlpJgdraetVgmAUc1 276 | # mjz2GCYX7rIliGkAJREKn4rV2MZzuGLEpTjz9dB+1Xp9Ndi9q3jQgs6k3IDIUube 277 | # YjPFFuPmFWRyi6oPTXmc4ExtTIewPvrOhwQ5q4ysxylkXoTS+UQt94BY2SvR+TMu 278 | # 6doU+0Y73xsO8Zz+lREh3fjBsDbPAgOV5989X6bmkJEEIwIK8LYgqvyED8XXTg== 279 | # SIG # End signature block 280 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator/RoslynExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.Text; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | 10 | namespace UnionTypes.Generators 11 | { 12 | internal static class RoslynExtensions 13 | { 14 | public static bool IsDeclaredInSource(this ITypeSymbol symbol) 15 | { 16 | return symbol.Locations.Any(loc => loc.IsInSource); 17 | } 18 | 19 | public static bool TryGetAttribute(this ISymbol symbol, string attributeName, out AttributeData attribute) 20 | { 21 | attribute = symbol.GetAttributes(attributeName).FirstOrDefault()!; 22 | return attribute != null; 23 | } 24 | 25 | public static IEnumerable GetAttributes(this ISymbol symbol, string attributeName) 26 | { 27 | if (!attributeName.EndsWith("Attribute")) 28 | attributeName += "Attribute"; 29 | 30 | return symbol.GetAttributes().Where(symbol => symbol.AttributeClass?.Name == attributeName)!; 31 | } 32 | 33 | public static bool TryGetConstructorArgument(this AttributeData attribute, int position, out TypedConstant argument) 34 | { 35 | if (attribute.ConstructorArguments.Length > position) 36 | { 37 | argument = attribute.ConstructorArguments[position]; 38 | return true; 39 | } 40 | 41 | argument = default; 42 | return false; 43 | } 44 | 45 | public static bool TryGetNamedArgument(this AttributeData attribute, string name, out TypedConstant argument) 46 | { 47 | if (attribute.NamedArguments.Any(na => na.Key == name)) 48 | { 49 | argument = attribute.NamedArguments.First(na => na.Key == name).Value; 50 | return true; 51 | } 52 | 53 | argument = default; 54 | return false; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/UnionSourceGenerator/UnionSourceGenerator/UnionSourceGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 11 6 | false 7 | enable 8 | 9 | UnionTypes.Toolkit.Generator.Library 10 | UnionTypes.Toolkit.Generator 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/UnionSourceGeneratorTests/RoslynExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace UnionTests 9 | { 10 | internal static class RoslynExtensions 11 | { 12 | public static SourceText InsertAt(this SourceText text, int index, string insert) 13 | { 14 | return text.WithChanges(new TextChange(new TextSpan(index, 0), insert)); 15 | } 16 | 17 | public static SourceText Append(this SourceText text, string append) 18 | { 19 | return text.WithChanges(new TextChange(new TextSpan(text.Length, 0), append)); 20 | } 21 | 22 | public static SourceText InsertAfter(this SourceText text, string find, string insert) 23 | { 24 | var index = text.ToString().IndexOf(find); 25 | if (index >= 0) 26 | { 27 | return text.WithChanges(new TextChange(new TextSpan(index + find.Length, 0), insert)); 28 | } 29 | return text; 30 | } 31 | 32 | public static SourceText InsertBefore(this SourceText text, string find, string insert) 33 | { 34 | var index = text.ToString().IndexOf(find); 35 | if (index >= 0) 36 | { 37 | return text.WithChanges(new TextChange(new TextSpan(index, 0), insert)); 38 | } 39 | return text; 40 | } 41 | 42 | public static SourceText ReplaceOne(this SourceText text, string find, string replacement) 43 | { 44 | var index = text.ToString().IndexOf(find); 45 | if (index >= 0) 46 | { 47 | return text.WithChanges(new TextChange(new TextSpan(index, find.Length), replacement)); 48 | } 49 | 50 | return text; 51 | } 52 | 53 | public static SourceText ReplaceAll(this SourceText text, string find, string replacement) 54 | { 55 | var locations = new List(); 56 | var str = text.ToString(); 57 | var index = str.IndexOf(find); 58 | while (index >= 0) 59 | { 60 | locations.Add(index); 61 | index = str.IndexOf(find, index + find.Length); 62 | } 63 | 64 | if (locations.Count > 0) 65 | { 66 | return text.WithChanges( 67 | locations.Select(loc => new TextChange(new TextSpan(loc, find.Length), replacement)) 68 | ); 69 | } 70 | 71 | return text; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/UnionSourceGeneratorTests/UnionSourceGeneratorTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/UnionSourceGeneratorTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Microsoft.VisualStudio.TestTools.UnitTesting; -------------------------------------------------------------------------------- /src/UnionTypes.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32804.467 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionTypes", "UnionTypes\UnionTypes.csproj", "{FAD0F6C5-9808-4AB0-8FDA-2CF9799DB151}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionSourceGeneratorTests", "UnionSourceGeneratorTests\UnionSourceGeneratorTests.csproj", "{E16F8F8C-A53E-400E-93D5-9098D5E6032E}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionSourceGenerator", "UnionSourceGenerator\UnionSourceGenerator\UnionSourceGenerator.csproj", "{49432D88-C8C9-4CFD-B70B-7C0B28B88CC3}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionSourceGenerator.Package", "UnionSourceGenerator\UnionSourceGenerator.Package\UnionSourceGenerator.Package.csproj", "{F77932CC-DCCC-4C66-BD71-E18BC2C0A839}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators", "Generators\Generators.csproj", "{2CC794DB-4BEF-40E7-A190-FA034922645A}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scratch", "Scratch\Scratch.csproj", "{F32A66C5-3C46-4DB3-B52B-88F173F13262}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnionGeneratorTests", "UnionGeneratorTests\UnionGeneratorTests.csproj", "{823FE1A8-19E6-4EE5-9F6A-C5A7AC7019F3}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnionTypesTests", "UnionTypesTests\UnionTypesTests.csproj", "{CB462558-5749-4A7D-A3C9-7335C0571B8C}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{83657EC2-1672-4DDE-92CF-71CC15B3E20F}" 23 | ProjectSection(SolutionItems) = preProject 24 | .editorconfig = .editorconfig 25 | ..\LICENSE = ..\LICENSE 26 | ..\README.md = ..\README.md 27 | EndProjectSection 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Release|Any CPU = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {FAD0F6C5-9808-4AB0-8FDA-2CF9799DB151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {FAD0F6C5-9808-4AB0-8FDA-2CF9799DB151}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {FAD0F6C5-9808-4AB0-8FDA-2CF9799DB151}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {FAD0F6C5-9808-4AB0-8FDA-2CF9799DB151}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {E16F8F8C-A53E-400E-93D5-9098D5E6032E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {E16F8F8C-A53E-400E-93D5-9098D5E6032E}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {E16F8F8C-A53E-400E-93D5-9098D5E6032E}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {E16F8F8C-A53E-400E-93D5-9098D5E6032E}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {49432D88-C8C9-4CFD-B70B-7C0B28B88CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {49432D88-C8C9-4CFD-B70B-7C0B28B88CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {49432D88-C8C9-4CFD-B70B-7C0B28B88CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {49432D88-C8C9-4CFD-B70B-7C0B28B88CC3}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {F77932CC-DCCC-4C66-BD71-E18BC2C0A839}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {F77932CC-DCCC-4C66-BD71-E18BC2C0A839}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {F77932CC-DCCC-4C66-BD71-E18BC2C0A839}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {F77932CC-DCCC-4C66-BD71-E18BC2C0A839}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {2CC794DB-4BEF-40E7-A190-FA034922645A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {2CC794DB-4BEF-40E7-A190-FA034922645A}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {2CC794DB-4BEF-40E7-A190-FA034922645A}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {2CC794DB-4BEF-40E7-A190-FA034922645A}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {F32A66C5-3C46-4DB3-B52B-88F173F13262}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {F32A66C5-3C46-4DB3-B52B-88F173F13262}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {F32A66C5-3C46-4DB3-B52B-88F173F13262}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {F32A66C5-3C46-4DB3-B52B-88F173F13262}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {823FE1A8-19E6-4EE5-9F6A-C5A7AC7019F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {823FE1A8-19E6-4EE5-9F6A-C5A7AC7019F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {823FE1A8-19E6-4EE5-9F6A-C5A7AC7019F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {823FE1A8-19E6-4EE5-9F6A-C5A7AC7019F3}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {CB462558-5749-4A7D-A3C9-7335C0571B8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {CB462558-5749-4A7D-A3C9-7335C0571B8C}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {CB462558-5749-4A7D-A3C9-7335C0571B8C}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {CB462558-5749-4A7D-A3C9-7335C0571B8C}.Release|Any CPU.Build.0 = Release|Any CPU 67 | EndGlobalSection 68 | GlobalSection(SolutionProperties) = preSolution 69 | HideSolutionNode = FALSE 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {2E069C9B-BE9B-4411-80D5-BBB8E6DCF7F0} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /src/UnionTypes/Decimal64.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.Numerics; 5 | 6 | namespace UnionTypes.Toolkit; 7 | 8 | /// 9 | /// A decimal value in 64 bits. 10 | /// Because, why not. 11 | /// 12 | public readonly struct Decimal64 : 13 | ISignedNumber, 14 | IComparable, 15 | IEquatable, 16 | IMinMaxValue, 17 | IParsable, 18 | ISpanParsable, 19 | IFormattable 20 | { 21 | private readonly long _bits; 22 | 23 | public static readonly long MaxMagnitude = 0x07FF_FFFF_FFFF_FFFF; 24 | public static readonly long MinMagnitude = ~MaxMagnitude; 25 | public static readonly byte MaxScale = 15; 26 | 27 | public static Decimal64 Zero => new Decimal64(0); 28 | public static Decimal64 One => new Decimal64(1, 0); 29 | public static Decimal64 NegativeOne => new Decimal64(-1, 0); 30 | public static Decimal64 MinValue => new Decimal64(MinMagnitude, 0); 31 | public static Decimal64 MaxValue => new Decimal64(MaxMagnitude, 0); 32 | public static Decimal64 AdditiveIdentity => Zero; 33 | public static Decimal64 MultiplicativeIdentity => One; 34 | 35 | /// 36 | /// Constructs a new given a magnitude and scale. 37 | /// The magnitude is the number as an integer without any decimal places. 38 | /// The scale is the number of decimal places. 39 | /// 40 | public Decimal64(long magnitude, byte scale) 41 | { 42 | if (magnitude < MinMagnitude || magnitude > MaxMagnitude) 43 | throw new ArgumentOutOfRangeException(nameof(magnitude)); 44 | if (scale < 0 || scale > 15) 45 | throw new ArgumentOutOfRangeException(nameof(scale)); 46 | _bits = magnitude << 4 | scale; 47 | } 48 | 49 | /// 50 | /// Constructs a new from bits. 51 | /// 52 | private Decimal64(long bits) 53 | { 54 | _bits = bits; 55 | } 56 | 57 | /// 58 | /// Constructs a new from bits encoded in a . 59 | /// 60 | public static Decimal64 FromBits(long bits) => new Decimal64(bits); 61 | 62 | /// 63 | /// Gets the encoded bits of the as a . 64 | /// 65 | public long GetBits() => _bits; 66 | 67 | /// 68 | /// The number of decimal places in the value, a value between 0 and 15. 69 | /// 70 | public byte Scale => (byte)(_bits & 0xF); 71 | 72 | /// 73 | /// The unscaled integer magnitude of the . 74 | /// 75 | public long Magnitude => _bits >> 4; 76 | 77 | /// 78 | /// True if the is an integer. 79 | /// 80 | public bool IsInteger => Scale == 0; 81 | 82 | /// 83 | /// Creates a from a value. 84 | /// Returns true if the value can be represented correctly in a . 85 | /// 86 | public static bool TryCreate(decimal value, out Decimal64 dec64) 87 | { 88 | var scale = value.Scale; 89 | if (scale <= 15) 90 | { 91 | // get unscaled magnitude (which may be larger than can fit in long) 92 | var magnitude = value * s_scaleFactor[scale]; 93 | 94 | if (magnitude >= MinMagnitude && magnitude <= MaxMagnitude) 95 | { 96 | dec64 = new Decimal64((long)magnitude, scale); 97 | return true; 98 | } 99 | } 100 | 101 | dec64 = default; 102 | return false; 103 | } 104 | 105 | // because indexing is faster than Math.Pow() 106 | private static readonly long[] s_scaleFactor = new long[] 107 | { 108 | 1, // 0 109 | 10, // 1 110 | 100, // 2 111 | 1000, // 3 112 | 10000, // 4 113 | 100000, // 5 114 | 1000000, // 6 115 | 10000000, // 7 116 | 100000000, // 8 117 | 1000000000, // 9 118 | 10000000000, // 10 119 | 100000000000, // 11 120 | 1000000000000, // 12 121 | 10000000000000, // 13 122 | 100000000000000, // 14 123 | 1000000000000000 // 15 124 | }; 125 | 126 | /// 127 | /// Creates a from a value. 128 | /// Returns true if the value can be represented correctly in a . 129 | /// 130 | public static bool TryCreate(long value, out Decimal64 dec64) 131 | { 132 | if (value >= MinMagnitude && value <= MaxMagnitude) 133 | { 134 | dec64 = new Decimal64(value, 0); 135 | return true; 136 | } 137 | 138 | dec64 = default; 139 | return false; 140 | } 141 | 142 | /// 143 | /// Returns the equivalant value given a value. 144 | /// Throws an if it is not possible. 145 | /// 146 | public static Decimal64 Create(decimal value) => 147 | TryCreate(value, out var dec64) ? dec64 : throw CreateOverflowException(); 148 | 149 | /// 150 | /// Returns the equivalant value given a value. 151 | /// Throws an if it is not possible. 152 | /// 153 | public static Decimal64 Create(long value) => 154 | TryCreate(value, out var dec64) ? dec64 : throw CreateOverflowException(); 155 | 156 | private static Exception CreateOverflowException() => 157 | new OverflowException($"The value cannot be represented in a {nameof(Decimal64)}."); 158 | 159 | /// 160 | /// Removes the decimal fraction. 161 | /// 162 | public readonly Decimal64 Truncate() 163 | { 164 | var tens = (int)Math.Pow(10, Math.Abs(Scale)); 165 | var newMagnitude = Magnitude / tens; 166 | return new Decimal64(newMagnitude, 0); 167 | } 168 | 169 | /// 170 | /// Converts the to a value. 171 | /// 172 | public readonly decimal ToDecimal() 173 | { 174 | var scale = (byte)(_bits & 0xF); 175 | var negative = _bits < 0; 176 | var magnitude = Math.Abs(_bits >> 4); 177 | var lo = unchecked((int)(magnitude & 0x0000_0000_FFFF_FFFF)); 178 | var mid = unchecked((int)((magnitude & 0x0FFF_FFFF_0000_0000) >> 32)); 179 | var d = new decimal(lo, mid, 0, negative, scale); 180 | return d; 181 | } 182 | 183 | 184 | #region Parsing and Formatting 185 | public static Decimal64 Parse(string s, IFormatProvider? provider) => 186 | Create(decimal.Parse(s, provider)); 187 | 188 | public static Decimal64 Parse(string s, NumberStyles style, IFormatProvider? provider) => 189 | Create(decimal.Parse(s, style, provider)); 190 | 191 | public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) 192 | { 193 | if (decimal.TryParse(s, provider, out var value) && TryCreate(value, out result)) 194 | return true; 195 | result = default; 196 | return false; 197 | } 198 | 199 | public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) 200 | { 201 | if (decimal.TryParse(s, style, provider, out var value) && TryCreate(value, out result)) 202 | return true; 203 | result = default; 204 | return false; 205 | } 206 | 207 | public static Decimal64 Parse(string s) => 208 | Create(decimal.Parse(s, null)); 209 | 210 | public static bool TryParse([NotNullWhen(true)] string? s, [MaybeNullWhen(false)] out Decimal64 result) => 211 | TryParse(s, null, out result); 212 | 213 | public static Decimal64 Parse(ReadOnlySpan s, IFormatProvider? provider) => 214 | Create(decimal.Parse(s, provider)); 215 | 216 | public static Decimal64 Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) => 217 | Create(decimal.Parse(s, style, provider)); 218 | 219 | public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) 220 | { 221 | if (decimal.TryParse(s, provider, out var value) && TryCreate(value, out result)) 222 | return true; 223 | result = default; 224 | return false; 225 | } 226 | 227 | public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) 228 | { 229 | if (decimal.TryParse(s, style, provider, out var value) && TryCreate(value, out result)) 230 | return true; 231 | result = default; 232 | return false; 233 | } 234 | 235 | public readonly override string ToString() => 236 | ToDecimal().ToString(); 237 | 238 | public readonly string ToString(string? format, IFormatProvider? formatProvider) => 239 | ToDecimal().ToString(format, formatProvider); 240 | 241 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 242 | ToDecimal().TryFormat(destination, out charsWritten, format, provider); 243 | 244 | #endregion 245 | 246 | #region Equality and Comparison 247 | 248 | public readonly bool Equals(Decimal64 other) => 249 | ToDecimal().Equals(other.ToDecimal()); 250 | 251 | public readonly bool Equals(decimal other) => 252 | ToDecimal().Equals(other); 253 | 254 | public readonly override bool Equals([NotNullWhen(true)] object? obj) => 255 | obj is Decimal64 sd && Equals(sd) 256 | || obj is decimal d && ToDecimal().Equals(d); 257 | 258 | public readonly override int GetHashCode() => 259 | ToDecimal().GetHashCode(); 260 | 261 | 262 | public static bool operator ==(Decimal64 a, Decimal64 b) => 263 | a.Equals(b); 264 | 265 | public static bool operator ==(Decimal64 a, decimal b) => 266 | a.Equals(b); 267 | 268 | public static bool operator ==(decimal a, Decimal64 b) => 269 | a.Equals(b.ToDecimal()); 270 | 271 | public static bool operator !=(Decimal64 a, Decimal64 b) => 272 | !a.Equals(b); 273 | 274 | public static bool operator !=(Decimal64 a, decimal b) => 275 | !a.Equals(b); 276 | 277 | public static bool operator !=(decimal a, Decimal64 b) => 278 | !a.Equals(b.ToDecimal()); 279 | 280 | 281 | public readonly int CompareTo(Decimal64 other) => 282 | ToDecimal().CompareTo(other.ToDecimal()); 283 | 284 | public readonly int CompareTo(decimal other) => 285 | ToDecimal().CompareTo(other); 286 | 287 | public static bool operator <(Decimal64 a, Decimal64 b) => 288 | a.ToDecimal() < b.ToDecimal(); 289 | 290 | 291 | public static bool operator >(Decimal64 a, Decimal64 b) => 292 | a.ToDecimal() > b.ToDecimal(); 293 | 294 | public static bool operator <(Decimal64 a, decimal b) => 295 | a.ToDecimal() < b; 296 | 297 | public static bool operator >(Decimal64 a, decimal b) => 298 | a.ToDecimal() > b; 299 | 300 | public static bool operator <(decimal a, Decimal64 b) => 301 | a < b.ToDecimal(); 302 | 303 | public static bool operator >(decimal a, Decimal64 b) => 304 | a > b.ToDecimal(); 305 | #endregion 306 | 307 | #region Conversion 308 | public static implicit operator decimal(Decimal64 dec64) => 309 | dec64.ToDecimal(); 310 | 311 | public static implicit operator Decimal64(int value) => 312 | Create((decimal)value); 313 | 314 | public static explicit operator Decimal64(long value) => 315 | Create((decimal)value); 316 | 317 | public static explicit operator Decimal64(decimal value) => 318 | Create(value); 319 | 320 | public static explicit operator long(Decimal64 value) => 321 | value.Truncate().Magnitude; 322 | #endregion 323 | 324 | #region Math operators 325 | public static Decimal64 operator +(Decimal64 a, Decimal64 b) => 326 | Create(a.ToDecimal() + b.ToDecimal()); 327 | 328 | public static decimal operator +(Decimal64 a, decimal b) => 329 | a.ToDecimal() + b; 330 | 331 | public static decimal operator +(decimal a, Decimal64 b) => 332 | a + b.ToDecimal(); 333 | 334 | public static Decimal64 operator +(Decimal64 a, long b) => 335 | Create(a.ToDecimal() + b); 336 | 337 | public static Decimal64 operator +(long a, Decimal64 b) => 338 | Create(a + b.ToDecimal()); 339 | 340 | public static Decimal64 operator -(Decimal64 a, Decimal64 b) => 341 | Create(a.ToDecimal() - b.ToDecimal()); 342 | 343 | public static decimal operator -(Decimal64 a, decimal b) => 344 | a.ToDecimal() - b; 345 | 346 | public static decimal operator -(decimal a, Decimal64 b) => 347 | a - b.ToDecimal(); 348 | 349 | public static Decimal64 operator -(Decimal64 a, long b) => 350 | Create(a.ToDecimal() - b); 351 | 352 | public static Decimal64 operator -(long a, Decimal64 b) => 353 | Create(a - b.ToDecimal()); 354 | 355 | public static Decimal64 operator *(Decimal64 a, Decimal64 b) => 356 | Create(a.ToDecimal() * b.ToDecimal()); 357 | 358 | public static decimal operator *(Decimal64 a, decimal b) => 359 | a.ToDecimal() * b; 360 | 361 | public static decimal operator *(decimal a, Decimal64 b) => 362 | a + b.ToDecimal(); 363 | 364 | public static Decimal64 operator *(Decimal64 a, long b) => 365 | Create(a.ToDecimal() * b); 366 | 367 | public static Decimal64 operator *(long a, Decimal64 b) => 368 | Create(a * b.ToDecimal()); 369 | 370 | 371 | public static Decimal64 operator /(Decimal64 a, Decimal64 b) => 372 | Create(a.ToDecimal() / b.ToDecimal()); 373 | 374 | public static decimal operator /(Decimal64 a, decimal b) => 375 | a.ToDecimal() / b; 376 | 377 | public static decimal operator /(decimal a, Decimal64 b) => 378 | a / b.ToDecimal(); 379 | 380 | public static Decimal64 operator /(Decimal64 a, long b) => 381 | Create(a.ToDecimal() / b); 382 | 383 | public static Decimal64 operator /(long a, Decimal64 b) => 384 | Create(a / b.ToDecimal()); 385 | 386 | public static Decimal64 operator ++(Decimal64 value) => 387 | new Decimal64(value.Magnitude + 1, value.Scale); 388 | 389 | public static Decimal64 operator --(Decimal64 value) => 390 | new Decimal64(value.Magnitude - 1, value.Scale); 391 | 392 | public static Decimal64 operator -(Decimal64 value) => 393 | new Decimal64(-value.Magnitude, value.Scale); 394 | 395 | public static Decimal64 operator +(Decimal64 value) => 396 | value; // why does this operator exist? 397 | #endregion 398 | 399 | #region ISignedNumber 400 | static Decimal64 INumberBase.Abs(Decimal64 value) => 401 | new Decimal64(Math.Abs(value.Magnitude), value.Scale); 402 | 403 | static bool INumberBase.IsCanonical(Decimal64 value) => 404 | true; 405 | 406 | static bool INumberBase.IsComplexNumber(Decimal64 value) => 407 | false; 408 | 409 | static bool INumberBase.IsEvenInteger(Decimal64 value) => 410 | value.Scale == 0 && (value.Magnitude & 1) == 0; 411 | 412 | static bool INumberBase.IsFinite(Decimal64 value) => 413 | true; 414 | 415 | static bool INumberBase.IsImaginaryNumber(Decimal64 value) => 416 | false; 417 | 418 | static bool INumberBase.IsInfinity(Decimal64 value) => 419 | false; 420 | 421 | static bool INumberBase.IsInteger(Decimal64 value) => 422 | value.Scale == 0; 423 | 424 | static bool INumberBase.IsNaN(Decimal64 value) => 425 | false; 426 | 427 | static bool INumberBase.IsNegative(Decimal64 value) => 428 | value.Magnitude < 0; 429 | 430 | static bool INumberBase.IsNegativeInfinity(Decimal64 value) => 431 | false; 432 | 433 | static bool INumberBase.IsNormal(Decimal64 value) => 434 | true; 435 | 436 | static bool INumberBase.IsOddInteger(Decimal64 value) => 437 | value.Scale == 0 && (value.Magnitude & 1) == 1; 438 | 439 | static bool INumberBase.IsPositive(Decimal64 value) => 440 | value.Magnitude >= 0; 441 | 442 | static bool INumberBase.IsPositiveInfinity(Decimal64 value) => 443 | false; 444 | 445 | static bool INumberBase.IsRealNumber(Decimal64 value) => 446 | true; 447 | 448 | static bool INumberBase.IsSubnormal(Decimal64 value) => 449 | false; 450 | 451 | static bool INumberBase.IsZero(Decimal64 value) => 452 | value == Zero; 453 | 454 | static Decimal64 INumberBase.MaxMagnitude(Decimal64 x, Decimal64 y) => 455 | x > y ? x : y; 456 | 457 | static Decimal64 INumberBase.MaxMagnitudeNumber(Decimal64 x, Decimal64 y) => 458 | x > y ? x : y; 459 | 460 | static Decimal64 INumberBase.MinMagnitude(Decimal64 x, Decimal64 y) => 461 | x < y ? x : y; 462 | 463 | static Decimal64 INumberBase.MinMagnitudeNumber(Decimal64 x, Decimal64 y) => 464 | x < y ? x : y; 465 | 466 | static int INumberBase.Radix => 10; 467 | 468 | static bool INumberBase.TryConvertFromChecked(TOther value, out Decimal64 result) 469 | { 470 | if (TOther.TryConvertToChecked(value, out var dval)) 471 | { 472 | result = TryCreate(dval, out result) 473 | ? result 474 | : throw CreateOverflowException(); 475 | return true; 476 | } 477 | 478 | result = default; 479 | return false; 480 | } 481 | 482 | static bool INumberBase.TryConvertFromTruncating(TOther value, out Decimal64 result) 483 | { 484 | if (TOther.TryConvertToTruncating(value, out var dval)) 485 | { 486 | if (TryCreate(dval, out result)) 487 | return true; 488 | result = (dval < 0.0m) ? MinValue : MaxValue; 489 | return true; 490 | } 491 | 492 | result = default; 493 | return false; 494 | } 495 | 496 | static bool INumberBase.TryConvertFromSaturating(TOther value, out Decimal64 result) 497 | { 498 | if (TOther.TryConvertToSaturating(value, out var dval)) 499 | { 500 | if (TryCreate(dval, out result)) 501 | return true; 502 | result = (dval < 0.0m) ? MinValue : MaxValue; 503 | return false; 504 | } 505 | 506 | result = default; 507 | return false; 508 | } 509 | 510 | static bool INumberBase.TryConvertToChecked(Decimal64 value, out TOther other) 511 | { 512 | return TOther.TryConvertFromChecked(value.ToDecimal(), out other!); 513 | } 514 | 515 | static bool INumberBase.TryConvertToTruncating(Decimal64 value, out TOther other) 516 | { 517 | return TOther.TryConvertFromTruncating(value.ToDecimal(), out other!); 518 | } 519 | 520 | static bool INumberBase.TryConvertToSaturating(Decimal64 value, out TOther other) 521 | { 522 | return TOther.TryConvertFromSaturating(value.ToDecimal(), out other!); 523 | } 524 | #endregion 525 | } 526 | -------------------------------------------------------------------------------- /src/UnionTypes/IOneOf.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace UnionTypes.Toolkit; 7 | 8 | public interface IOneOf : ITypeUnion 9 | { 10 | /// 11 | /// The value held by the union. 12 | /// 13 | object Value { get; } 14 | } -------------------------------------------------------------------------------- /src/UnionTypes/ITypeUnion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace UnionTypes.Toolkit 6 | { 7 | /// 8 | /// A type union holds a single value of one or more possible types 9 | /// that need not be from the same inheritance hierarchy. 10 | /// 11 | public interface ITypeUnion 12 | { 13 | /// 14 | /// The current type of the value held by the union. 15 | /// 16 | Type Type { get; } 17 | 18 | /// 19 | /// Gets the union's value as the specified type. 20 | /// Returns true if the value is successfully retrieved. 21 | /// 22 | bool TryGet([NotNullWhen(true)] out T value); 23 | } 24 | 25 | /// 26 | /// A type union that can create itself from a value. 27 | /// 28 | public interface ITypeUnion : ITypeUnion 29 | { 30 | /// 31 | /// Creates the union from the specified value, if possible. 32 | /// Returns true if the union is successfully created from the value. 33 | /// 34 | abstract static bool TryCreate(TValue value, [NotNullWhen(true)] out TSelf union); 35 | } 36 | 37 | /// 38 | /// A type union that has a closed set of possible types. 39 | /// 40 | public interface IClosedTypeUnion : ITypeUnion 41 | { 42 | /// 43 | /// The closed set of possible types the union's value may take. 44 | /// 45 | static abstract IReadOnlyList Types { get; } 46 | } 47 | } -------------------------------------------------------------------------------- /src/UnionTypes/None.cs: -------------------------------------------------------------------------------- 1 | namespace UnionTypes.Toolkit; 2 | 3 | /// 4 | /// This type represents the no-value case for the type . 5 | /// You may use it in your own type union for the same purpose. 6 | /// 7 | public class None 8 | { 9 | private None() {} 10 | public static readonly None Singleton = new None(); 11 | } 12 | 13 | /// 14 | /// This type can be used to represent the undefined case for any type union 15 | /// without a default case. 16 | /// 17 | public class Undefined 18 | { 19 | private Undefined() { } 20 | public static readonly Undefined Singleton = new Undefined(); 21 | } -------------------------------------------------------------------------------- /src/UnionTypes/OneOf_generated.tt: -------------------------------------------------------------------------------- 1 | <#@ template compilerOptions="/d:T4" debug="true" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ include file="..\Generators\Generator.cs" #> 8 | <#@ include file="..\Generators\OneOfGenerator.cs" #> 9 | <#= OneOfGenerator.Generate(9, "UnionTypes.Toolkit") #> 10 | 11 | -------------------------------------------------------------------------------- /src/UnionTypes/Option.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace UnionTypes.Toolkit 6 | { 7 | public static class Option 8 | { 9 | public static Option Some(TValue value) => 10 | Option.Some(value); 11 | } 12 | 13 | public partial struct Option 14 | { 15 | /// 16 | /// True when the option has a value. 17 | /// 18 | public bool IsSome => this.Kind == Case.Some; 19 | 20 | /// 21 | /// True when the option has no value. 22 | /// 23 | public bool IsNone => this.Kind == Case.None; 24 | 25 | /// 26 | /// Maps a value to a new value or returns the default value. 27 | /// 28 | public Option Map(Func map) => 29 | this.Kind switch 30 | { 31 | Case.Some => Option.Some(map(this.Value)), 32 | Case.None => default, 33 | _ => throw new InvalidOperationException("Unknown case."), 34 | }; 35 | 36 | /// 37 | /// Maps a value to a new value or returns the default value. 38 | /// 39 | public Option Map(Func> map) => 40 | this.Kind switch 41 | { 42 | Case.Some => map(this.Value), 43 | Case.None => default, 44 | _ => throw new InvalidOperationException("Unknown case."), 45 | }; 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/UnionTypes/Option_generated.cs: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Runtime.InteropServices; 7 | using UnionTypes.Toolkit; 8 | #nullable enable 9 | 10 | namespace UnionTypes.Toolkit 11 | { 12 | public partial struct Option : IClosedTypeUnion>, IEquatable> 13 | { 14 | public enum Case 15 | { 16 | Some = 1, 17 | None = 0, 18 | } 19 | 20 | public Case Kind { get; } 21 | private readonly TValue _data_some_value; 22 | 23 | private Option(Case kind, TValue some_value) 24 | { 25 | this.Kind = kind; 26 | _data_some_value = some_value; 27 | } 28 | 29 | public static Option Some(TValue value) => new Option(kind: Option.Case.Some, some_value: value); 30 | public static Option None() => new Option(kind: Option.Case.None, some_value: default!); 31 | 32 | public static implicit operator Option(TValue value) => Option.Some(value); 33 | public static implicit operator Option(UnionTypes.Toolkit.None value) => Option.None(); 34 | 35 | public static bool TryCreate(TCreate value, out Option union) 36 | { 37 | switch (value) 38 | { 39 | case TValue v: union = Option.Some(v); return true; 40 | case UnionTypes.Toolkit.None v: union = Option.None(); return true; 41 | } 42 | return TypeUnion.TryCreateFromUnion(value, out union); 43 | } 44 | 45 | /// Accessible when is . 46 | public TValue Value => this.Kind == Option.Case.Some ? _data_some_value : default!; 47 | 48 | public bool TryGet([NotNullWhen(true)] out TGet value) 49 | { 50 | switch (this.Kind) 51 | { 52 | case Option.Case.Some: 53 | if (this.Value is TGet tvSome) 54 | { 55 | value = tvSome; 56 | return true; 57 | } 58 | return TypeUnion.TryCreate(this.Value, out value); 59 | case Option.Case.None: 60 | if (global::UnionTypes.Toolkit.None.Singleton is TGet tvNone) 61 | { 62 | value = tvNone; 63 | return true; 64 | } 65 | return TypeUnion.TryCreate(global::UnionTypes.Toolkit.None.Singleton, out value); 66 | } 67 | value = default!; 68 | return false; 69 | } 70 | 71 | public Type Type 72 | { 73 | get 74 | { 75 | switch (this.Kind) 76 | { 77 | case Option.Case.Some: return typeof(TValue); 78 | case Option.Case.None: return typeof(UnionTypes.Toolkit.None); 79 | } 80 | return typeof(object); 81 | } 82 | } 83 | 84 | static IReadOnlyList IClosedTypeUnion>.Types { get; } = 85 | new [] { typeof(TValue), typeof(UnionTypes.Toolkit.None) }; 86 | 87 | public bool Equals(Option other) 88 | { 89 | if (this.Kind != other.Kind) return false; 90 | 91 | switch (this.Kind) 92 | { 93 | case Option.Case.Some: 94 | return object.Equals(this.Value, other.Value); 95 | case Option.Case.None: 96 | return true; 97 | default: 98 | return false; 99 | } 100 | } 101 | 102 | public override bool Equals(object? other) 103 | { 104 | return TryCreate(other, out var union) && this.Equals(union); 105 | } 106 | 107 | public override int GetHashCode() 108 | { 109 | switch (this.Kind) 110 | { 111 | case Option.Case.Some: 112 | return this.Value?.GetHashCode() ?? 0; 113 | case Option.Case.None: 114 | return (int)this.Kind; 115 | default: 116 | return 0; 117 | } 118 | } 119 | 120 | public static bool operator == (Option left, Option right) => left.Equals(right); 121 | public static bool operator != (Option left, Option right) => !left.Equals(right); 122 | 123 | public override string ToString() 124 | { 125 | switch (this.Kind) 126 | { 127 | case Option.Case.Some: 128 | return this.Value?.ToString() ?? ""; 129 | case Option.Case.None: 130 | return global::UnionTypes.Toolkit.None.Singleton?.ToString() ?? ""; 131 | default: 132 | return ""; 133 | } 134 | } 135 | 136 | public void Match(Action whenSome, Action whenNone) 137 | { 138 | switch (Kind) 139 | { 140 | case Option.Case.Some : whenSome(this.Value); break; 141 | case Option.Case.None : whenNone(); break; 142 | default: throw new InvalidOperationException("Invalid union state"); 143 | } 144 | } 145 | 146 | public TResult Select(Func whenSome, Func whenNone) 147 | { 148 | switch (Kind) 149 | { 150 | case Option.Case.Some: return whenSome(this.Value); 151 | case Option.Case.None: return whenNone(); 152 | default: throw new InvalidOperationException("Invalid union state"); 153 | } 154 | } 155 | } 156 | } 157 | 158 | -------------------------------------------------------------------------------- /src/UnionTypes/Option_generated.tt: -------------------------------------------------------------------------------- 1 | <#@ template compilerOptions="/d:T4" debug="true" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ include file="..\Generators\Generator.cs" #> 8 | <#@ include file="..\Generators\UnionGenerator.cs" #> 9 | <#= UnionGenerator.Generate( 10 | new Union( 11 | UnionKind.TypeUnion, 12 | "Option", 13 | "Option", 14 | "public partial", 15 | new[] 16 | { 17 | new UnionCase( 18 | name: "Some", 19 | tagValue: 1, 20 | factoryName: "Some", 21 | factoryParameters: new [] { new UnionCaseValue("value", new UnionValueType("TValue", TypeKind.TypeParameter_Unconstrained)) }, 22 | accessorName: "Value"), 23 | new UnionCase( 24 | name: "None", 25 | type: new UnionValueType("UnionTypes.Toolkit.None", TypeKind.Class, "Singleton"), 26 | tagValue: 0, 27 | factoryName:"None", 28 | hasAccessor: false) 29 | }, 30 | UnionOptions.Default 31 | .WithUseToolkit(true) 32 | .WithGenerateMatch(true) 33 | .WithGenerateEquality(true) 34 | .WithGenerateToString(true) 35 | ), 36 | namespaceName: "UnionTypes.Toolkit" 37 | ) 38 | #> 39 | -------------------------------------------------------------------------------- /src/UnionTypes/ReadMe.Nuget.md: -------------------------------------------------------------------------------- 1 | # UnionTypes.Toolkit 2 | 3 | A collection of common union types and tools for building custom ones. 4 | 5 | [Learn about using the toolkit in your project here.](https://github.com/mattwar/UnionTypes.Toolkit) 6 | -------------------------------------------------------------------------------- /src/UnionTypes/Result.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnionTypes.Toolkit 4 | { 5 | public partial struct Result 6 | { 7 | /// 8 | /// True when the result is undefined. 9 | /// 10 | public bool IsInvalid => this.Kind == 0; 11 | 12 | /// 13 | /// Map the success value to a new value or result 14 | /// 15 | public Result Map(Func> whenSuccess) => 16 | this.Kind switch 17 | { 18 | Case.Success => whenSuccess(this.Value), 19 | Case.Failure => Result.Failure(this.Error), 20 | _ => default // still invalid 21 | }; 22 | 23 | /// 24 | /// Map the success value to a new success value. 25 | /// 26 | public Result Map(Func whenSuccess) => 27 | this.Kind switch 28 | { 29 | Case.Success => Result.Success(whenSuccess(this.Value)), 30 | Case.Failure => Result.Failure(this.Error), 31 | _ => default // still invalid 32 | }; 33 | } 34 | } -------------------------------------------------------------------------------- /src/UnionTypes/Result_generated.cs: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Runtime.InteropServices; 7 | using UnionTypes.Toolkit; 8 | #nullable enable 9 | 10 | namespace UnionTypes.Toolkit 11 | { 12 | public partial struct Result : IClosedTypeUnion>, IEquatable> 13 | { 14 | public enum Case 15 | { 16 | Success = 1, 17 | Failure = 2, 18 | } 19 | 20 | public Case Kind { get; } 21 | private readonly TValue _data_success_value; 22 | private readonly TError _data_failure_error; 23 | 24 | private Result(Case kind, TValue success_value, TError failure_error) 25 | { 26 | this.Kind = kind; 27 | _data_success_value = success_value; 28 | _data_failure_error = failure_error; 29 | } 30 | 31 | public static Result Success(TValue value) => new Result(kind: Result.Case.Success, success_value: value, failure_error: default!); 32 | public static Result Failure(TError error) => new Result(kind: Result.Case.Failure, success_value: default!, failure_error: error); 33 | 34 | public static implicit operator Result(TValue value) => Result.Success(value); 35 | public static implicit operator Result(TError value) => Result.Failure(value); 36 | 37 | public static bool TryCreate(TCreate value, out Result union) 38 | { 39 | switch (value) 40 | { 41 | case TValue v: union = Result.Success(v); return true; 42 | case TError v: union = Result.Failure(v); return true; 43 | } 44 | return TypeUnion.TryCreateFromUnion(value, out union); 45 | } 46 | 47 | /// Accessible when is . 48 | public TValue Value => this.Kind == Result.Case.Success ? _data_success_value : default!; 49 | 50 | /// Accessible when is . 51 | public TError Error => this.Kind == Result.Case.Failure ? _data_failure_error : default!; 52 | 53 | public bool TryGet([NotNullWhen(true)] out TGet value) 54 | { 55 | switch (this.Kind) 56 | { 57 | case Result.Case.Success: 58 | if (this.Value is TGet tvSuccess) 59 | { 60 | value = tvSuccess; 61 | return true; 62 | } 63 | return TypeUnion.TryCreate(this.Value, out value); 64 | case Result.Case.Failure: 65 | if (this.Error is TGet tvFailure) 66 | { 67 | value = tvFailure; 68 | return true; 69 | } 70 | return TypeUnion.TryCreate(this.Error, out value); 71 | } 72 | value = default!; 73 | return false; 74 | } 75 | 76 | public Type Type 77 | { 78 | get 79 | { 80 | switch (this.Kind) 81 | { 82 | case Result.Case.Success: return typeof(TValue); 83 | case Result.Case.Failure: return typeof(TError); 84 | } 85 | return typeof(object); 86 | } 87 | } 88 | 89 | static IReadOnlyList IClosedTypeUnion>.Types { get; } = 90 | new [] { typeof(TValue), typeof(TError) }; 91 | 92 | public bool Equals(Result other) 93 | { 94 | if (this.Kind != other.Kind) return false; 95 | 96 | switch (this.Kind) 97 | { 98 | case Result.Case.Success: 99 | return object.Equals(this.Value, other.Value); 100 | case Result.Case.Failure: 101 | return object.Equals(this.Error, other.Error); 102 | default: 103 | return false; 104 | } 105 | } 106 | 107 | public override bool Equals(object? other) 108 | { 109 | return TryCreate(other, out var union) && this.Equals(union); 110 | } 111 | 112 | public override int GetHashCode() 113 | { 114 | switch (this.Kind) 115 | { 116 | case Result.Case.Success: 117 | return this.Value?.GetHashCode() ?? 0; 118 | case Result.Case.Failure: 119 | return this.Error?.GetHashCode() ?? 0; 120 | default: 121 | return 0; 122 | } 123 | } 124 | 125 | public static bool operator == (Result left, Result right) => left.Equals(right); 126 | public static bool operator != (Result left, Result right) => !left.Equals(right); 127 | 128 | public override string ToString() 129 | { 130 | switch (this.Kind) 131 | { 132 | case Result.Case.Success: 133 | return this.Value?.ToString() ?? ""; 134 | case Result.Case.Failure: 135 | return this.Error?.ToString() ?? ""; 136 | default: 137 | return ""; 138 | } 139 | } 140 | 141 | public void Match(Action whenSuccess, Action whenFailure, Action? undefined = null) 142 | { 143 | switch (Kind) 144 | { 145 | case Result.Case.Success : whenSuccess(this.Value); break; 146 | case Result.Case.Failure : whenFailure(this.Error); break; 147 | default: if (undefined != null) undefined(); else throw new InvalidOperationException("Undefined union state."); break; 148 | } 149 | } 150 | 151 | public TResult Select(Func whenSuccess, Func whenFailure, Func? undefined = null) 152 | { 153 | switch (Kind) 154 | { 155 | case Result.Case.Success: return whenSuccess(this.Value); 156 | case Result.Case.Failure: return whenFailure(this.Error); 157 | default: return undefined != null ? undefined() : throw new InvalidOperationException("Undefined union state."); 158 | } 159 | } 160 | } 161 | } 162 | 163 | -------------------------------------------------------------------------------- /src/UnionTypes/Result_generated.tt: -------------------------------------------------------------------------------- 1 | <#@ template compilerOptions="/d:T4" debug="true" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <#@ include file="..\Generators\Generator.cs" #> 8 | <#@ include file="..\Generators\UnionGenerator.cs" #> 9 | <#= UnionGenerator.Generate( 10 | new Union( 11 | UnionKind.TypeUnion, 12 | "Result", 13 | "Result", 14 | "public partial", 15 | new[] 16 | { 17 | new UnionCase( 18 | name: "Success", 19 | tagValue: 1, 20 | factoryName:"Success", 21 | factoryParameters: new [] { new UnionCaseValue("value", new UnionValueType("TValue", TypeKind.TypeParameter_Unconstrained)) }, 22 | accessorName: "Value"), 23 | new UnionCase( 24 | name: "Failure", 25 | tagValue: 2, 26 | factoryName: "Failure", 27 | factoryParameters: new [] { new UnionCaseValue("error", new UnionValueType("TError", TypeKind.TypeParameter_Unconstrained)) }, 28 | accessorName: "Error") 29 | }, 30 | UnionOptions.Default 31 | .WithUseToolkit(true) 32 | .WithGenerateMatch(true) 33 | .WithGenerateEquality(true) 34 | .WithGenerateToString(true) 35 | ), 36 | namespaceName: "UnionTypes.Toolkit" 37 | ) 38 | #> 39 | -------------------------------------------------------------------------------- /src/UnionTypes/TypeUnion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Collections.Generic; 5 | using System.Collections.Immutable; 6 | using System.Linq; 7 | using System.Threading; 8 | 9 | namespace UnionTypes.Toolkit 10 | { 11 | /// 12 | /// Helper functions for interacting with types that implement . 13 | /// 14 | public static partial class TypeUnion 15 | { 16 | /// 17 | /// Returns true if an instance of the specfieid type can be successfully created from the value of type . 18 | /// 19 | public static bool CanCreate(TValue value, Type unionType) 20 | { 21 | return GetHelper(unionType).CanCreate(value); 22 | } 23 | 24 | /// 25 | /// Returns true if type can be successfully created from the value of type . 26 | /// 27 | public static bool TryCreate(TValue value, [NotNullWhen(true)] out TUnion union) 28 | { 29 | // try creating union from value 30 | var unionHelper = GetHelper(); 31 | if (unionHelper.TryCreate(value, out union)) 32 | return true; 33 | 34 | if (value is ITypeUnion) 35 | { 36 | return TryCreateFromUnion(value, out union); 37 | } 38 | 39 | return false; 40 | } 41 | 42 | /// 43 | /// Gets the value of the union as the type if possible 44 | /// or by creating the value from the union's value if the is a type union. 45 | /// 46 | public static bool TryGet(TUnion union, [NotNullWhen(true)] out TValue value) 47 | { 48 | // try getting the value from the union as the requested type 49 | var unionHelper = GetHelper(); 50 | if (unionHelper.TryGet(union, out value)) 51 | return true; 52 | 53 | // if the value is also a union, try creating it from the union's value. 54 | return TryCreateFromUnion(union, out value); 55 | } 56 | 57 | /// 58 | /// Creates the target union from the source . 59 | /// 60 | public static bool TryCreateFromUnion(TSourceUnion source, [NotNullWhen(true)] out TTargetUnion target) 61 | { 62 | var sourceHelper = GetHelper(); 63 | return sourceHelper.TryCreateFrom(source, out target); 64 | } 65 | 66 | /// 67 | /// Gets the known case types from the union type . 68 | /// 69 | public static IReadOnlyList GetCaseTypes() 70 | { 71 | return GetHelper().GetCaseTypes(); 72 | } 73 | 74 | /// 75 | /// Current set of known type union helpers. 76 | /// 77 | private static ImmutableDictionary _helpers = 78 | ImmutableDictionary.Empty; 79 | 80 | /// 81 | /// Gets the current converter for the target type. 82 | /// 83 | private static Helper GetHelper(Type unionType) 84 | { 85 | if (!_helpers.TryGetValue(unionType, out var converter)) 86 | { 87 | converter = ImmutableInterlocked.GetOrAdd(ref _helpers, unionType, tt => CreateHelper(tt)); 88 | } 89 | 90 | return converter; 91 | } 92 | 93 | private static Helper GetHelper() 94 | { 95 | return (Helper)GetHelper(typeof(TUnion)); 96 | } 97 | 98 | /// 99 | /// Creates the helper for the type 100 | /// 101 | private static Helper CreateHelper(Type unionType) 102 | { 103 | if (unionType.GetInterfaces().Any(iface => 104 | iface.IsGenericType 105 | && iface.GetGenericTypeDefinition() == typeof(IClosedTypeUnion<>) 106 | && iface.GenericTypeArguments[0] == unionType)) 107 | { 108 | var utc = typeof(ClosedTypeUnionTHelper<>).MakeGenericType(unionType); 109 | return (Helper?)Activator.CreateInstance(utc)!; 110 | } 111 | else if (unionType.GetInterfaces().Any(iface => 112 | iface.IsGenericType 113 | && iface.GetGenericTypeDefinition() == typeof(ITypeUnion<>) 114 | && iface.GenericTypeArguments[0] == unionType)) 115 | { 116 | var utc = typeof(TypeUnionTHelper<>).MakeGenericType(unionType); 117 | return (Helper?)Activator.CreateInstance(utc)!; 118 | } 119 | else if (unionType.IsAssignableTo(typeof(ITypeUnion))) 120 | { 121 | var utc = typeof(TypeUnionHelper<>).MakeGenericType(unionType); 122 | return (Helper?)Activator.CreateInstance(utc)!; 123 | } 124 | else 125 | { 126 | return (Helper?)Activator.CreateInstance(typeof(DefaultHelper<>).MakeGenericType(unionType))!; 127 | } 128 | } 129 | 130 | private abstract class Helper 131 | { 132 | /// 133 | /// Returns true if the value can be converted into the the type converter's target type. 134 | /// 135 | public abstract bool CanCreate(TValue value); 136 | } 137 | 138 | private abstract class Helper : Helper 139 | { 140 | public override bool CanCreate(TValue value) 141 | { 142 | return value is TUnion; 143 | } 144 | 145 | public virtual bool TryCreate(TValue value, [NotNullWhen(true)] out TUnion union) 146 | { 147 | // allow reference conversions only 148 | if (value is TUnion tunion) 149 | { 150 | union = tunion; 151 | return true; 152 | } 153 | else 154 | { 155 | union = default!; 156 | return false; 157 | } 158 | } 159 | 160 | public virtual IReadOnlyList GetCaseTypes() 161 | { 162 | return Array.Empty(); 163 | } 164 | 165 | public virtual bool TryGet(TUnion union, [NotNullWhen(true)] out TValue value) 166 | { 167 | if (union is TValue tvalue) 168 | { 169 | value = tvalue; 170 | return true; 171 | } 172 | else 173 | { 174 | value = default!; 175 | return false; 176 | } 177 | } 178 | 179 | public virtual bool TryCreateFrom(TOther other, [NotNullWhen(true)] out TUnion union) 180 | { 181 | union = default!; 182 | return false; 183 | } 184 | } 185 | 186 | private class TypeUnionHelper : Helper 187 | where TUnion : ITypeUnion 188 | { 189 | public override bool TryGet(TUnion union, [NotNullWhen(true)] out TValue value) 190 | { 191 | return union.TryGet(out value); 192 | } 193 | 194 | public override bool TryCreateFrom(TOther other, [NotNullWhen(true)] out TUnion union) 195 | { 196 | // not creatable... 197 | union = default!; 198 | return false; 199 | } 200 | } 201 | 202 | private class TypeUnionTHelper : TypeUnionHelper 203 | where TUnion : ITypeUnion 204 | { 205 | public override bool CanCreate(TValue value) 206 | { 207 | return TUnion.TryCreate(value, out _); 208 | } 209 | 210 | public override bool TryCreate(TValue value, [NotNullWhen(true)] out TUnion union) 211 | { 212 | return TUnion.TryCreate(value, out union); 213 | } 214 | 215 | public override bool TryCreateFrom(TOther other, [NotNullWhen(true)] out TUnion union) 216 | { 217 | if (other is ITypeUnion u) 218 | { 219 | var helper = GetCreateFromHelper(u.Type); 220 | return helper.TryCreateFrom(other, out union); 221 | } 222 | union = default!; 223 | return false; 224 | } 225 | 226 | private static ImmutableDictionary _createFromHelpers = 227 | ImmutableDictionary.Empty; 228 | 229 | private static CreateFromUnionHelper GetCreateFromHelper(Type valueType) 230 | { 231 | if (!_createFromHelpers.TryGetValue(typeof(TOther), out var helper)) 232 | { 233 | var tmp = CreateGetUnionHelper(valueType); 234 | helper = ImmutableInterlocked.GetOrAdd(ref _createFromHelpers, typeof(TOther), tmp); 235 | } 236 | 237 | return (CreateFromUnionHelper)helper; 238 | } 239 | 240 | private static CreateFromUnionHelper CreateGetUnionHelper(Type valueType) 241 | { 242 | return (CreateFromUnionHelper)Activator.CreateInstance(typeof(CreateFromUnionHelper<,>).MakeGenericType(typeof(TUnion), typeof(TOther), valueType))!; 243 | } 244 | 245 | private protected abstract class CreateFromUnionHelper { } 246 | private protected abstract class CreateFromUnionHelper : CreateFromUnionHelper 247 | { 248 | public abstract bool TryCreateFrom(TOther other, [NotNullWhen(true)] out TUnion union); 249 | } 250 | 251 | private class CreateFromUnionHelper : CreateFromUnionHelper 252 | where TOther : ITypeUnion 253 | { 254 | public override bool TryCreateFrom(TOther other, [NotNullWhen(true)] out TUnion union) 255 | { 256 | if (other.TryGet(out TValue value) && TUnion.TryCreate(value, out union)) 257 | return true; 258 | union = default!; 259 | return false; 260 | } 261 | } 262 | } 263 | 264 | private class ClosedTypeUnionTHelper : TypeUnionTHelper 265 | where TUnion : IClosedTypeUnion 266 | { 267 | public override bool TryGet(TUnion union, [NotNullWhen(true)] out TValue value) 268 | { 269 | if (union.TryGet(out value)) 270 | return true; 271 | 272 | return base.TryGet(union, out value); 273 | } 274 | 275 | public override IReadOnlyList GetCaseTypes() 276 | { 277 | return TUnion.Types; 278 | } 279 | } 280 | 281 | /// 282 | /// A default type factory that only supports reference conversions. 283 | /// 284 | private class DefaultHelper : Helper 285 | { 286 | } 287 | } 288 | } -------------------------------------------------------------------------------- /src/UnionTypes/TypeUnionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnionTypes.Toolkit; 4 | 5 | public static class TypeUnionExtensions 6 | { 7 | /// 8 | /// Converts this union's value to the target type or union. 9 | /// 10 | public static bool TryConvertTo(this TUnion union, out TValue value) 11 | where TUnion : ITypeUnion 12 | { 13 | return TypeUnion.TryConvert(union, out value); 14 | } 15 | } -------------------------------------------------------------------------------- /src/UnionTypes/UnionAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UnionTypes.Toolkit 5 | { 6 | public abstract class UnionAttributeBase : Attribute 7 | { 8 | /// 9 | /// Share data fields of the same type across cases. 10 | /// 11 | public bool ShareSameTypeFields { get; set; } 12 | 13 | /// 14 | /// Share data fields of reference type across cases. 15 | /// 16 | public bool ShareReferenceFields { get; set; } 17 | 18 | /// 19 | /// Overlap structs with only overlappable fields with other case data. 20 | /// 21 | public bool OverlapStructs { get; set; } 22 | 23 | /// 24 | /// Allow structs declared outside the current compilation unit to be overlapped with other case data. 25 | /// 26 | public bool OverlapForeignStructs { get; set; } 27 | 28 | /// 29 | /// Decompose structs (records and tuples) into their constituent data to allow better data compaction. 30 | /// 31 | public bool DecomposeStructs { get; set; } 32 | 33 | /// 34 | /// Allow structs declared outside the current compilation unit to be decomposed. 35 | /// 36 | public bool DecomposeForeignStructs { get; set; } 37 | 38 | /// 39 | /// Generate pass through implementations of IEquatable, Equals and GetHashCode. 40 | /// 41 | public bool GenerateEquality { get; set; } 42 | 43 | /// 44 | /// Generate Match methods that force handling of all cases. 45 | /// 46 | public bool GenerateMatch { get; set; } 47 | 48 | /// 49 | /// Generate pass through implemention of ToString() 50 | /// 51 | public bool GenerateToString { get; set; } 52 | 53 | /// 54 | /// The type name of the tag enum generated within the union type. 55 | /// Default is 'Case'. 56 | /// 57 | public string? TagTypeName { get; set; } 58 | 59 | /// 60 | /// The property name for the union's current tag, generated on the union type. 61 | /// Default is 'Kind'. 62 | /// 63 | public string? TagPropertyName { get; set; } 64 | } 65 | 66 | [AttributeUsage(AttributeTargets.Struct)] 67 | public class TypeUnionAttribute : UnionAttributeBase 68 | { 69 | public TypeUnionAttribute() 70 | { 71 | } 72 | } 73 | 74 | [AttributeUsage(AttributeTargets.Struct)] 75 | public class TagUnionAttribute : UnionAttributeBase 76 | { 77 | public TagUnionAttribute() 78 | { 79 | } 80 | } 81 | 82 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = true)] 83 | public class CaseAttribute : Attribute 84 | { 85 | /// 86 | /// The name of the case used in factory methods, accessors and tags. 87 | /// If not assigned the name is derived from the member it is associated with. 88 | /// 89 | public string? Name { get; set; } = null; 90 | 91 | /// 92 | /// The value of the tag associated with the case. 93 | /// A value of 0 will allow the case to be default when the union is defaulted or unassigned. 94 | /// 95 | public int TagValue { get; set; } = -1; 96 | 97 | /// 98 | /// The name of the factory method to construct a union of this case. 99 | /// If not specified it is [FactoryPrefix][CaseName]. 100 | /// 101 | public string? FactoryName { get; set; } = null; 102 | 103 | /// 104 | /// The kind of factory to generate when the case attribute is not placed on the factory. 105 | /// 106 | public string? FactoryKind { get; set; } = null; 107 | 108 | /// 109 | /// The name of the member to access the value(s) of this case. 110 | /// 111 | public string? AccessorName { get; set; } = null; 112 | 113 | /// 114 | /// The kind of accessor to generate for the case. 115 | /// 116 | public string? AccessorKind { get; set; } = null; 117 | 118 | /// 119 | /// The type of the type union case when it cannot be infered. 120 | /// 121 | public Type? Type { get; set; } = null; 122 | 123 | public CaseAttribute() 124 | { 125 | } 126 | } 127 | 128 | public static class FactoryKind 129 | { 130 | /// 131 | /// The factory generated is a method taking zero or more arguments corresponding the the case values. 132 | /// 133 | public const string Method = nameof(Method); 134 | 135 | /// 136 | /// No factory is generated for this case. 137 | /// This is only available for cases that have no data and correspond to the default state. 138 | /// 139 | public const string None = nameof(None); 140 | 141 | /// 142 | /// The factory generated is a property. 143 | /// This is only available for cases with no data or a single value of a singleton type. 144 | /// 145 | public const string Property = nameof(Property); 146 | } 147 | 148 | public static class AccessorKind 149 | { 150 | /// 151 | /// The accessor generated is a method returning a single value. 152 | /// Cases with multiple values return a tuple. 153 | /// 154 | public const string Method = nameof(Method); 155 | 156 | /// 157 | /// No accessor is generated for this case. 158 | /// This is possible for cases without data. 159 | /// 160 | public const string None = nameof(None); 161 | 162 | /// 163 | /// The accessor generated is a property. 164 | /// Cases with multiple values return a tuple. 165 | /// 166 | public const string Property = nameof(Property); 167 | } 168 | } -------------------------------------------------------------------------------- /src/UnionTypes/UnionTypes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | True 8 | UnionTypes.Toolkit 9 | True 10 | True 11 | Matt Warren 12 | A collection of common union types and tools to build custom ones. 13 | https://github.com/mattwar/UnionTypes.Toolkit 14 | ReadMe.Nuget.md 15 | https://github.com/mattwar/UnionTypes.Toolkit 16 | git 17 | LICENSE 18 | True 19 | 20 | 21 | 22 | 23 | True 24 | \ 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | True 35 | \ 36 | 37 | 38 | Result_generated.cs 39 | TextTemplatingFileGenerator 40 | 41 | 42 | TextTemplatingFileGenerator 43 | Option_generated.cs 44 | 45 | 46 | TextTemplatingFileGenerator 47 | OneOf_generated.cs 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | 60 | 61 | True 62 | True 63 | Option_generated.tt 64 | 65 | 66 | True 67 | True 68 | 69 | 70 | True 71 | True 72 | OneOf_generated.tt 73 | 74 | 75 | True 76 | True 77 | Result_generated.tt 78 | 79 | 80 | True 81 | True 82 | ShapeGenerator.tt 83 | 84 | 85 | True 86 | True 87 | StructShape.tt 88 | 89 | 90 | True 91 | True 92 | StructShapeGenerator.tt 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/UnionTypes/build_nuget.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%1"=="" goto usage 3 | dotnet build -c:release -p:version=%1 -p:packageversion=%1 4 | goto done 5 | 6 | :usage 7 | echo usage: build_nuget build_version 8 | 9 | :done 10 | -------------------------------------------------------------------------------- /src/UnionTypesTests/ConversionTests.cs: -------------------------------------------------------------------------------- 1 | using UnionTypes.Toolkit; 2 | 3 | namespace UnionTests 4 | { 5 | [TestClass] 6 | public class ConversionTests 7 | { 8 | [TestMethod] 9 | public void TestTryConvert_Variant_To_Value() 10 | { 11 | // convert a variant holding one type to a value of a different type 12 | Variant variant = 3; 13 | TypeUnion.TryConvert(variant, out long lval); 14 | Assert.AreEqual(3L, lval); 15 | } 16 | 17 | [TestMethod] 18 | public void TestTryConvert_OneOf_To_Value() 19 | { 20 | OneOf union = 3; 21 | TypeUnion.TryConvert(union, out long lval); 22 | Assert.AreEqual(3L, lval); 23 | } 24 | 25 | [TestMethod] 26 | public void TestTryConvert_Value_To_OneOf() 27 | { 28 | TypeUnion.TryConvert("3", out OneOf u); 29 | Assert.AreEqual(1, u.Kind); 30 | Assert.AreEqual(3, u.Type1Value); 31 | Assert.AreEqual(typeof(int), u.Type); 32 | Assert.AreEqual(3, u.Value); 33 | } 34 | 35 | [TestMethod] 36 | public void TestTryConvert_OneOf_To_OneOf() 37 | { 38 | OneOf union = 3; 39 | TypeUnion.TryConvert(union, out OneOf u); 40 | Assert.AreEqual(1, u.Kind); 41 | Assert.AreEqual(3L, u.Type1Value); 42 | Assert.AreEqual(typeof(long), u.Type); 43 | Assert.AreEqual(3L, u.Value); 44 | } 45 | 46 | [TestMethod] 47 | public void TestTryConvert_Value_To_Value() 48 | { 49 | TypeUnion.TryConvert("3", out double v); 50 | Assert.AreEqual(3.0, v); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/UnionTypesTests/MSTestSettings.cs: -------------------------------------------------------------------------------- 1 | [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] 2 | -------------------------------------------------------------------------------- /src/UnionTypesTests/OneOfTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using UnionTypes.Toolkit; 3 | 4 | namespace UnionTests 5 | { 6 | [TestClass] 7 | public class OneOfTests 8 | { 9 | [TestMethod] 10 | public void Test_OneOf2() 11 | { 12 | TestOneOf2(1, 1, 1, typeof(int)); 13 | TestOneOf2("one", "one", 2, typeof(string)); 14 | TestOneOf2(default(OneOf), null, 0, typeof(object)); 15 | } 16 | 17 | [TestMethod] 18 | public void Test_OneOf3() 19 | { 20 | TestOneOf3(1, 1, 1, typeof(int)); 21 | TestOneOf3("one", "one", 2, typeof(string)); 22 | TestOneOf3(3.0, 3.0, 3, typeof(double)); 23 | } 24 | 25 | [TestMethod] 26 | public void Test_Equality() 27 | { 28 | OneOf value = 1; 29 | Assert.IsTrue(1 == value); 30 | Assert.IsTrue(value == 1); 31 | Assert.IsFalse(value == 2); 32 | } 33 | 34 | [TestMethod] 35 | public void Test_TryCreate_Union() 36 | { 37 | OneOf a = 1; 38 | Assert.IsTrue(OneOf.TryCreate(a, out var b)); 39 | Assert.AreEqual(typeof(int), b.Type); 40 | } 41 | 42 | [TestMethod] 43 | public void Test_TryGet_Union() 44 | { 45 | OneOf a = 1; 46 | Assert.IsTrue(a.TryGet(out OneOf b)); 47 | Assert.AreEqual(1, b.Value); 48 | Assert.IsFalse(a.TryGet(out OneOf c)); 49 | Assert.IsTrue(a.TryGet(out Variant v)); // variant should succeed on all values 50 | Assert.AreEqual(1, v.Value); 51 | } 52 | 53 | private void TestOneOf2( 54 | OneOf oneOf, object? expectedValue, int expectedKind, Type expectedType) 55 | { 56 | Assert.AreEqual(oneOf.Value, expectedValue); 57 | Assert.AreEqual(expectedKind, oneOf.Kind); 58 | Assert.AreSame(oneOf.Type, expectedType); 59 | TestOneOf(oneOf, expectedValue, expectedType); 60 | } 61 | 62 | private void TestOneOf3( 63 | OneOf oneOf, object? expectedValue, int expectedKind, Type expectedType) 64 | { 65 | Assert.AreEqual(oneOf.Value, expectedValue); 66 | Assert.AreEqual(expectedKind, oneOf.Kind); 67 | Assert.AreSame(oneOf.Type, expectedType); 68 | TestOneOf(oneOf, expectedValue, expectedType); 69 | } 70 | 71 | private void TestOneOf(TOneOf oneOf, object? expectedValue, Type expectedType) 72 | where TOneOf : IOneOf, IClosedTypeUnion 73 | { 74 | Assert.AreEqual(oneOf.Value, expectedValue); 75 | Assert.AreEqual(oneOf.Type, expectedType); 76 | } 77 | 78 | private class OneOfTest 79 | where TOneOf : IOneOf, IClosedTypeUnion 80 | { 81 | public static OneOfTest Instance { get; } = new(); 82 | 83 | public static void Test(TOneOf oneOf, TExpected expectedValue, int expectedKind) 84 | { 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/UnionTypesTests/OptionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using UnionTypes.Toolkit; 3 | 4 | namespace UnionTests 5 | { 6 | [TestClass] 7 | public class OptionTests 8 | { 9 | [TestMethod] 10 | public void Test_AssignedDefault() 11 | { 12 | Option zed = default; 13 | Assert.IsTrue(zed.IsNone); 14 | } 15 | 16 | [TestMethod] 17 | public void Test_AssignedNone() 18 | { 19 | Option zed = None.Singleton; 20 | Assert.AreEqual(Option.Case.None, zed.Kind); 21 | Assert.IsTrue(zed.IsNone); 22 | } 23 | 24 | [TestMethod] 25 | public void Test_NoneFactory() 26 | { 27 | var zed = Option.None(); 28 | Assert.AreEqual(Option.Case.None, zed.Kind); 29 | Assert.IsTrue(zed.IsNone); 30 | } 31 | 32 | [TestMethod] 33 | public void Test_AssignedValue() 34 | { 35 | Option zed = 10; 36 | Assert.AreEqual(Option.Case.Some, zed.Kind); 37 | Assert.AreEqual(10, zed.Value); 38 | } 39 | 40 | [TestMethod] 41 | public void Test_SomeFactory() 42 | { 43 | var zed = Option.Some(10); 44 | Assert.AreEqual(Option.Case.Some, zed.Kind); 45 | Assert.AreEqual(10, zed.Value); 46 | } 47 | 48 | [TestMethod] 49 | public void Test_SomeHelper() 50 | { 51 | var zed = Option.Some(10); 52 | Assert.AreEqual(Option.Case.Some, zed.Kind); 53 | Assert.AreEqual(10, zed.Value); 54 | } 55 | 56 | [TestMethod] 57 | public void Test_ValueAccessFromGet() 58 | { 59 | Option zed = 10; 60 | Assert.AreEqual(Option.Case.Some, zed.Kind); 61 | Assert.IsTrue(zed.TryGet(out int value)); 62 | Assert.AreEqual(10, value); 63 | } 64 | 65 | [TestMethod] 66 | public void Test_NoneAccessFromGet() 67 | { 68 | Option zed = None.Singleton; 69 | Assert.AreEqual(Option.Case.None, zed.Kind); 70 | Assert.IsTrue(zed.TryGet(out None value)); 71 | Assert.AreSame(None.Singleton, value); 72 | } 73 | 74 | [TestMethod] 75 | public void Test_Select_Value() 76 | { 77 | Option zed = 10; 78 | Assert.IsTrue(zed.Select(value => true, () => false)); 79 | } 80 | 81 | [TestMethod] 82 | public void Test_Match_Value() 83 | { 84 | Option zed = 10; 85 | zed.Match(value => { }, () => { Assert.Fail("Expected a value"); }); 86 | } 87 | 88 | [TestMethod] 89 | public void Test_Select_None() 90 | { 91 | Option zed = default; 92 | Assert.IsFalse(zed.Select(value => true, () => false)); 93 | } 94 | 95 | [TestMethod] 96 | public void Test_Match_None() 97 | { 98 | Option zed = default; 99 | zed.Match(value => { Assert.Fail("Expected None"); }, () => { }); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/UnionTypesTests/ResultTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using UnionTypes.Toolkit; 3 | 4 | namespace UnionTests 5 | { 6 | [TestClass] 7 | public class ResultTests 8 | { 9 | [TestMethod] 10 | public void Test_AssignableFromValue() 11 | { 12 | Result result = 10; 13 | Assert.AreEqual(Result.Case.Success, result.Kind); 14 | Assert.AreEqual(10, result.Value); 15 | } 16 | 17 | [TestMethod] 18 | public void Test_AssignableFromError() 19 | { 20 | var whoops = new Exception("Whoops"); 21 | Result result = whoops; 22 | Assert.AreEqual(Result.Case.Failure, result.Kind); 23 | Assert.AreSame(whoops, result.Error); 24 | } 25 | 26 | [TestMethod] 27 | public void Test_DefaultIsInvalid() 28 | { 29 | Result result = default; 30 | Assert.IsTrue(result.IsInvalid); 31 | } 32 | 33 | [TestMethod] 34 | public void Test_TryGet_Value() 35 | { 36 | Result result = 10; 37 | Assert.AreEqual(Result.Case.Success, result.Kind); 38 | Assert.IsTrue(result.TryGet(out int value)); 39 | Assert.AreEqual(10, value); 40 | } 41 | 42 | [TestMethod] 43 | public void Test_TryGet_Error() 44 | { 45 | var whoops = new Exception("Whoops"); 46 | Result result = whoops; 47 | Assert.AreEqual(Result.Case.Failure, result.Kind); 48 | Assert.IsTrue(result.TryGet(out Exception error)); 49 | Assert.AreEqual(whoops, error); 50 | } 51 | 52 | [TestMethod] 53 | public void Test_Ambiguous_Value() 54 | { 55 | Result result = Result.Success("Success"); 56 | Assert.AreEqual(Result.Case.Success, result.Kind); 57 | Assert.AreEqual("Success", result.Value); 58 | } 59 | 60 | [TestMethod] 61 | public void Test_Ambiguous_Error() 62 | { 63 | Result result = Result.Failure("Whoops"); 64 | Assert.AreEqual(Result.Case.Failure, result.Kind); 65 | Assert.AreEqual("Whoops", result.Error); 66 | } 67 | 68 | [TestMethod] 69 | public void Test_Select_Value() 70 | { 71 | Result result = 10; 72 | Assert.IsTrue(result.Select(value => true, error => false)); 73 | } 74 | 75 | [TestMethod] 76 | public void Test_Select_Error() 77 | { 78 | Result result = new Exception("Whoops"); 79 | Assert.IsTrue(result.Select(value => false, error => true)); 80 | } 81 | 82 | [TestMethod] 83 | public void Test_Select_Invalid() 84 | { 85 | Result result = default; 86 | Assert.IsTrue(result.Select(value => false, error => false, () => true)); 87 | } 88 | 89 | [TestMethod] 90 | public void Test_Match_Value() 91 | { 92 | Result result = 10; 93 | result.Match(value => { Assert.AreEqual(10, value); }, error => { Assert.Fail("Not value"); }, () => { Assert.Fail("Not value"); }); 94 | } 95 | 96 | [TestMethod] 97 | public void Test_Match_Error() 98 | { 99 | var whoops = new Exception("Whoops"); 100 | Result result = whoops; 101 | result.Match(value => { Assert.Fail("Not error"); }, error => { Assert.AreEqual(whoops, error); }, () => { Assert.Fail("Not error"); }); 102 | } 103 | 104 | [TestMethod] 105 | public void Test_Match_Invalid() 106 | { 107 | Result result = default; 108 | result.Match(value => { Assert.Fail("Not invalid"); }, error => { Assert.Fail("Not invalid"); }, () => { }); 109 | } 110 | 111 | [TestMethod] 112 | public void Test_Map_ValueToValue() 113 | { 114 | Result result = 10; 115 | var mapped = result.Map(value => value * 2); 116 | Assert.AreEqual(Result.Case.Success, mapped.Kind); 117 | Assert.AreEqual(20, mapped.Value); 118 | } 119 | 120 | [TestMethod] 121 | public void Test_Map_Error() 122 | { 123 | var whoops = new Exception("Whoops"); 124 | Result result = whoops; 125 | var mapped = result.Map(value => value * 2); 126 | Assert.AreEqual(Result.Case.Failure, mapped.Kind); 127 | Assert.AreEqual(whoops, mapped.Error); 128 | } 129 | 130 | [TestMethod] 131 | public void Test_Map_ValueToError() 132 | { 133 | var whoops = new Exception("Whoops"); 134 | Result result = 10; 135 | var mapped = result.Map(value => Result.Failure(whoops)); 136 | Assert.AreEqual(Result.Case.Failure, mapped.Kind); 137 | Assert.AreEqual(whoops, mapped.Error); 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/UnionTypesTests/UnionTypesTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/UnionTypesTests/VariantTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using UnionTypes.Toolkit; 3 | 4 | namespace UnionTests; 5 | 6 | [TestClass] 7 | public class VariantTests 8 | { 9 | [TestMethod] 10 | public void Test_GenericCreateAndAccess() 11 | { 12 | TestGenericCreateAndAccess(true); 13 | TestGenericCreateAndAccess(false); 14 | TestGenericCreateAndAccess(true); 15 | TestGenericCreateAndAccess(false); 16 | TestGenericCreateAndAccess(null); 17 | 18 | TestGenericCreateAndAccess(1); 19 | TestGenericCreateAndAccess(0); 20 | TestGenericCreateAndAccess(sbyte.MinValue); 21 | TestGenericCreateAndAccess(sbyte.MaxValue); 22 | TestGenericCreateAndAccess(1); 23 | TestGenericCreateAndAccess(null); 24 | 25 | TestGenericCreateAndAccess(1); 26 | TestGenericCreateAndAccess(0); 27 | TestGenericCreateAndAccess(short.MinValue); 28 | TestGenericCreateAndAccess(short.MaxValue); 29 | TestGenericCreateAndAccess(1); 30 | TestGenericCreateAndAccess(null); 31 | 32 | TestGenericCreateAndAccess(1); 33 | TestGenericCreateAndAccess(0); 34 | TestGenericCreateAndAccess(int.MinValue); 35 | TestGenericCreateAndAccess(int.MaxValue); 36 | TestGenericCreateAndAccess(1); 37 | TestGenericCreateAndAccess(null); 38 | 39 | TestGenericCreateAndAccess(1); 40 | TestGenericCreateAndAccess(0); 41 | TestGenericCreateAndAccess(long.MinValue); 42 | TestGenericCreateAndAccess(long.MaxValue); 43 | TestGenericCreateAndAccess(1); 44 | TestGenericCreateAndAccess(null); 45 | 46 | TestGenericCreateAndAccess(1); 47 | TestGenericCreateAndAccess(byte.MinValue); 48 | TestGenericCreateAndAccess(byte.MaxValue); 49 | TestGenericCreateAndAccess(1); 50 | TestGenericCreateAndAccess(null); 51 | 52 | TestGenericCreateAndAccess(1); 53 | TestGenericCreateAndAccess(ushort.MinValue); 54 | TestGenericCreateAndAccess(ushort.MaxValue); 55 | TestGenericCreateAndAccess(1); 56 | TestGenericCreateAndAccess(null); 57 | 58 | TestGenericCreateAndAccess(1); 59 | TestGenericCreateAndAccess(uint.MinValue); 60 | TestGenericCreateAndAccess(uint.MaxValue); 61 | TestGenericCreateAndAccess(1); 62 | TestGenericCreateAndAccess(null); 63 | 64 | TestGenericCreateAndAccess(1); 65 | TestGenericCreateAndAccess(ulong.MinValue); 66 | TestGenericCreateAndAccess(ulong.MaxValue); 67 | TestGenericCreateAndAccess(1); 68 | TestGenericCreateAndAccess(null); 69 | 70 | TestGenericCreateAndAccess(1.0f); 71 | TestGenericCreateAndAccess(0.0f); 72 | TestGenericCreateAndAccess(float.MinValue); 73 | TestGenericCreateAndAccess(float.MaxValue); 74 | TestGenericCreateAndAccess(float.Epsilon); 75 | TestGenericCreateAndAccess(float.PositiveInfinity); 76 | TestGenericCreateAndAccess(float.NegativeInfinity); 77 | TestGenericCreateAndAccess(float.NaN); 78 | TestGenericCreateAndAccess(1.0f); 79 | TestGenericCreateAndAccess(null); 80 | 81 | TestGenericCreateAndAccess(1.0); 82 | TestGenericCreateAndAccess(0.0); 83 | TestGenericCreateAndAccess(double.MinValue); 84 | TestGenericCreateAndAccess(double.MaxValue); 85 | TestGenericCreateAndAccess(double.Epsilon); 86 | TestGenericCreateAndAccess(double.PositiveInfinity); 87 | TestGenericCreateAndAccess(double.NegativeInfinity); 88 | TestGenericCreateAndAccess(double.NaN); 89 | TestGenericCreateAndAccess(1.0); 90 | TestGenericCreateAndAccess(null); 91 | 92 | TestGenericCreateAndAccess(Decimal64.Create(1.0m)); 93 | TestGenericCreateAndAccess(Decimal64.Create(0.0m)); 94 | TestGenericCreateAndAccess(Decimal64.MinValue); 95 | TestGenericCreateAndAccess(Decimal64.MaxValue); 96 | TestGenericCreateAndAccess(Decimal64.Create(1.0m)); 97 | TestGenericCreateAndAccess(null); 98 | 99 | TestGenericCreateAndAccess(1.0m); 100 | TestGenericCreateAndAccess(0.0m); 101 | TestGenericCreateAndAccess(decimal.MinValue, isBoxed: true); 102 | TestGenericCreateAndAccess(decimal.MaxValue, isBoxed: true); 103 | TestGenericCreateAndAccess(1.0m); 104 | TestGenericCreateAndAccess(null); 105 | 106 | TestGenericCreateAndAccess('1'); 107 | TestGenericCreateAndAccess('\0'); 108 | TestGenericCreateAndAccess(char.MinValue); 109 | TestGenericCreateAndAccess(char.MaxValue); 110 | TestGenericCreateAndAccess('1'); 111 | TestGenericCreateAndAccess(null); 112 | 113 | TestGenericCreateAndAccess(new Rune('1')); 114 | TestGenericCreateAndAccess(new Rune('\0')); 115 | TestGenericCreateAndAccess(new Rune('1')); 116 | TestGenericCreateAndAccess(null); 117 | 118 | TestGenericCreateAndAccess(new DateOnly(2002, 4, 15)); 119 | TestGenericCreateAndAccess(DateOnly.MinValue); 120 | TestGenericCreateAndAccess(DateOnly.MaxValue); 121 | TestGenericCreateAndAccess(new DateOnly(2002, 4, 15)); 122 | TestGenericCreateAndAccess(null); 123 | 124 | TestGenericCreateAndAccess(new TimeOnly(10, 53)); 125 | TestGenericCreateAndAccess(TimeOnly.MinValue); 126 | TestGenericCreateAndAccess(TimeOnly.MaxValue); 127 | TestGenericCreateAndAccess(new TimeOnly(10, 53)); 128 | TestGenericCreateAndAccess(null); 129 | 130 | TestGenericCreateAndAccess(DateTime.Now); 131 | TestGenericCreateAndAccess(DateTime.MinValue); 132 | TestGenericCreateAndAccess(DateTime.MaxValue); 133 | TestGenericCreateAndAccess(DateTime.Now); 134 | TestGenericCreateAndAccess(null); 135 | 136 | TestGenericCreateAndAccess(TimeSpan.FromMinutes(53)); 137 | TestGenericCreateAndAccess(TimeSpan.MinValue); 138 | TestGenericCreateAndAccess(TimeSpan.MaxValue); 139 | TestGenericCreateAndAccess(TimeSpan.FromMinutes(53)); 140 | TestGenericCreateAndAccess(null); 141 | 142 | TestGenericCreateAndAccess("string"); 143 | TestGenericCreateAndAccess(""); 144 | TestGenericCreateAndAccess("string"); 145 | TestGenericCreateAndAccess(null); 146 | 147 | TestGenericCreateAndAccess(Guid.NewGuid(), isBoxed: true); 148 | TestGenericCreateAndAccess(Guid.NewGuid(), isBoxed: true); 149 | TestGenericCreateAndAccess(null); 150 | 151 | TestGenericCreateAndAccess(new ReferenceType("one", 1)); 152 | TestGenericCreateAndAccess(new ReferenceType("one", 1)); 153 | TestGenericCreateAndAccess(null); 154 | 155 | TestGenericCreateAndAccess(new WrapperStruct("one")); 156 | TestGenericCreateAndAccess(new WrapperStruct("one")); 157 | TestGenericCreateAndAccess(null); 158 | 159 | TestGenericCreateAndAccess(new SmallStructMixed("one", 1), isBoxed: true); 160 | TestGenericCreateAndAccess(new SmallStructMixed("one", 1), isBoxed: true); 161 | TestGenericCreateAndAccess(null); 162 | 163 | TestGenericCreateAndAccess(new LargeStructMixed("one", 1, "two", 2), isBoxed: true); 164 | TestGenericCreateAndAccess(new LargeStructMixed("one", 1, "two", 2), isBoxed: true); 165 | TestGenericCreateAndAccess(null); 166 | 167 | TestGenericCreateAndAccess(new SmallStructNoRefs(1, 2)); 168 | TestGenericCreateAndAccess(new SmallStructNoRefs(1, 2)); 169 | TestGenericCreateAndAccess(null); 170 | 171 | TestGenericCreateAndAccess(new LargeStructNoRefs(1, 2, 3, 4), isBoxed: true); 172 | TestGenericCreateAndAccess(new LargeStructNoRefs(1, 2, 3, 4), isBoxed: true); 173 | TestGenericCreateAndAccess(null); 174 | 175 | // enums 176 | TestGenericCreateAndAccess(I8Enum.A); 177 | TestGenericCreateAndAccess(I16Enum.A); 178 | TestGenericCreateAndAccess(I32Enum.A); 179 | TestGenericCreateAndAccess(I64Enum.A); 180 | TestGenericCreateAndAccess(UI8Enum.A); 181 | TestGenericCreateAndAccess(UI16Enum.A); 182 | TestGenericCreateAndAccess(UI32Enum.A); 183 | TestGenericCreateAndAccess(UI64Enum.A); 184 | } 185 | 186 | private record ReferenceType(string a, int b); 187 | private record struct SmallStructMixed(string a, int b); 188 | private record struct WrapperStruct(string a); 189 | private record struct SmallStructNoRefs(int a, int b); 190 | private record struct LargeStructNoRefs(int a, int b, int c, int d); 191 | private record struct LargeStructMixed(string a, int b, string c, int d); 192 | 193 | private enum I8Enum : sbyte { A = 1, B, C }; 194 | private enum I16Enum : short { A = 1, B, C }; 195 | private enum I32Enum : int { A = 1, B, C }; 196 | private enum I64Enum : long { A = 1, B, C }; 197 | private enum UI8Enum : byte { A = 1, B, C }; 198 | private enum UI16Enum : ushort { A = 1, B, C }; 199 | private enum UI32Enum : uint { A = 1, B, C }; 200 | private enum UI64Enum : ulong { A = 1, B, C }; 201 | 202 | private void TestGenericCreateAndAccess(T value, bool isBoxed = false) 203 | { 204 | Assert.IsTrue(Variant.TryCreate(value, out var v)); 205 | var v2 = Variant.Create(value); 206 | 207 | if (value == null) 208 | { 209 | Assert.AreEqual(typeof(object), v.Type, "Type"); 210 | Assert.IsTrue(v.IsNull, "IsNull"); 211 | Assert.AreEqual(isBoxed, v.IsBoxed, "IsBoxed"); 212 | Assert.IsFalse(v.TryGet(out _), "TryGet"); 213 | Assert.ThrowsException(() => v.Get()); 214 | Assert.AreEqual(default, v.GetOrDefault()); 215 | } 216 | else 217 | { 218 | var nonNullT = GetNonNullableType(typeof(T)); 219 | Assert.AreEqual(nonNullT, v.Type, "Type"); 220 | Assert.IsFalse(v.IsNull, "IsNull"); 221 | Assert.AreEqual(isBoxed, v.IsBoxed, "IsBoxed"); 222 | Assert.IsTrue(v.TryGet(out var actualValue), "TryGet"); 223 | Assert.AreEqual(value, v.Get()); 224 | Assert.AreEqual(value, v.GetOrDefault()); 225 | } 226 | } 227 | 228 | /// 229 | /// Returns true if the type is Nullable<T> 230 | /// 231 | private static bool IsNullableType(Type type) => 232 | type.IsGenericType 233 | && type.GetGenericTypeDefinition() == typeof(Nullable<>); 234 | 235 | /// 236 | /// If the type is Nullable<T>, returns the type T, 237 | /// otherwise returns the type. 238 | /// 239 | private static Type GetNonNullableType(Type type) => 240 | IsNullableType(type) 241 | ? type.GetGenericArguments()[0] 242 | : type; 243 | 244 | [TestMethod] 245 | public void Test_NonGenericAPI_Int32() 246 | { 247 | Variant v = Variant.Create(10); 248 | var ival = v.Int32Value; 249 | Assert.AreEqual(10, ival); 250 | 251 | var ival2 = v.Get(); 252 | Assert.AreEqual(ival, ival2); 253 | 254 | var v2 = Variant.Create(10); 255 | var ival3 = v2.Int32Value; 256 | Assert.AreEqual(ival, ival3); 257 | } 258 | 259 | [TestMethod] 260 | public void Test_NonGenericAPI_String() 261 | { 262 | Variant v = Variant.Create("ten"); 263 | var success = v.TryGet(out string? sval); 264 | Assert.IsTrue(success); 265 | Assert.AreEqual("ten", sval); 266 | 267 | var sval2 = v.Get(); 268 | Assert.AreEqual(sval, sval2); 269 | } 270 | 271 | [TestMethod] 272 | public void Test_GetOrDefault() 273 | { 274 | TestGetOrDefault_NullStruct(); 275 | TestGetOrDefault_NullStruct(); 276 | TestGetOrDefault_NullStruct(); 277 | TestGetOrDefault_NullStruct(); 278 | TestGetOrDefault_NullStruct(); 279 | TestGetOrDefault_NullStruct(); 280 | TestGetOrDefault_NullStruct(); 281 | TestGetOrDefault_NullStruct(); 282 | TestGetOrDefault_NullStruct(); 283 | TestGetOrDefault_NullStruct(); 284 | TestGetOrDefault_NullStruct(); 285 | TestGetOrDefault_NullStruct(); 286 | TestGetOrDefault_NullStruct(); 287 | TestGetOrDefault_NullStruct(); 288 | TestGetOrDefault_NullStruct(); 289 | TestGetOrDefault_NullStruct(); 290 | TestGetOrDefault_NullStruct(); 291 | TestGetOrDefault_NullStruct(); 292 | TestGetOrDefault_NullStruct(); 293 | TestGetOrDefault_NullStruct(); 294 | TestGetOrDefault_NullStruct(); 295 | TestGetOrDefault_NullStruct(); 296 | TestGetOrDefault_NullStruct(); 297 | TestGetOrDefault_NullStruct(); 298 | 299 | TestGetOrDefault_NullRef(); 300 | TestGetOrDefault_NullRef(); 301 | } 302 | 303 | private void TestGetOrDefault_NullStruct() 304 | where T : struct 305 | { 306 | Assert.AreEqual((T?)null, Variant.Null.GetOrDefault()); 307 | } 308 | 309 | private void TestGetOrDefault_NullRef() 310 | where T : class 311 | { 312 | Assert.AreEqual((T?)null, Variant.Null.GetOrDefault()); 313 | } 314 | 315 | [TestMethod] 316 | public void Test_Create_Union() 317 | { 318 | var v = Variant.Create(OneOf.Create(1)); 319 | Assert.AreEqual(typeof(int), v.Type); 320 | Assert.AreEqual(VariantKind.Int32, v.Kind); 321 | Assert.AreEqual(1, v.Int32Value); 322 | Assert.AreNotEqual(1L, v.Int64Value); 323 | } 324 | 325 | [TestMethod] 326 | public void Test_TryCreate_Union() 327 | { 328 | Assert.IsTrue(Variant.TryCreate(OneOf.Create(1), out var v)); 329 | Assert.AreEqual(typeof(int), v.Type); 330 | Assert.AreEqual(VariantKind.Int32, v.Kind); 331 | Assert.AreEqual(1, v.Int32Value); 332 | Assert.AreNotEqual(1L, v.Int64Value); 333 | } 334 | 335 | [TestMethod] 336 | public void Test_TryGet_Union() 337 | { 338 | var v = Variant.Create(1); 339 | Assert.IsTrue(v.TryGet(out OneOf u)); 340 | Assert.AreEqual(1, u.Value); 341 | } 342 | } --------------------------------------------------------------------------------