{"id":13727561,"url":"https://github.com/SleepWellPupper/Unions","last_synced_at":"2025-05-07T23:31:49.318Z","repository":{"id":206789989,"uuid":"717210699","full_name":"PaulBraetz/Unions","owner":"PaulBraetz","description":null,"archived":false,"fork":false,"pushed_at":"2024-05-06T17:42:09.000Z","size":351,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-08-04T02:07:36.608Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PaulBraetz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-11-10T20:19:29.000Z","updated_at":"2024-05-07T22:50:13.000Z","dependencies_parsed_at":"2023-11-23T18:25:47.383Z","dependency_job_id":"77ac2f4a-8559-4125-95f5-284482218fb1","html_url":"https://github.com/PaulBraetz/Unions","commit_stats":null,"previous_names":["paulbraetz/unions"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulBraetz%2FUnions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulBraetz%2FUnions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulBraetz%2FUnions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulBraetz%2FUnions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PaulBraetz","download_url":"https://codeload.github.com/PaulBraetz/Unions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224672780,"owners_count":17350806,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T02:00:22.744Z","updated_at":"2025-05-07T23:31:44.001Z","avatar_url":"https://github.com/PaulBraetz.png","language":"C#","readme":"# This Repo Is Obsoleted By: https://github.com/PaulBraetz/RhoMicro.CodeAnalysis/tree/release/UnionsGenerator\n\n# Unions\n\nRead about union types here: https://en.wikipedia.org/wiki/Union_type\n\n## Table of Contents\n\n1. [Features](#Features)\n2. [Alternative Union Type Implementations](#Alternative-Union-Type-Implementations)\n3. [Installation](#Installation)\n4. [How To Use](#How-To-Use)\n5. [Contrived Example](#Contrived-Example)\n6. [Why Not OneOf?](#Why-Not-OneOf?)\n\n## Features\n\n- generate rich examination and conversion api\n- automatic relation type detection (congruency, superset, subset, intersection)\n- generate conversion operators\n- generate meaningful api names like `myUnion.IsResult` or `MyUnion.CreateFromResult(result)`\n- generate the most efficient impementation for your usecase and optimize against boxing or size constraints\n\n## Alternative Union Type Implementations\n \n- [OneOf](https://github.com/mcintyre321/OneOf)\n- [ValueVariant](https://github.com/hikarin522/ValueVariant)\n- [DiscriminatedUnion](https://github.com/sdedalus/DiscriminatedUnion)\n- [CSharpDiscriminatedUnion](https://github.com/Galad/CSharpDiscriminatedUnion)\n- [UnionType](https://github.com/Cricle/UnionType)\n- [Funcky Discriminated Union](https://github.com/polyadic/funcky-discriminated-union)\n- [N.SourceGenerators.UnionTypes](https://github.com/Ne4to/N.SourceGenerators.UnionTypes) \u003c- This one is really similar to mine\n\n## Installation\n\nRequirements: `net7` (due to `static abstract` members)\n\nPackage Reference:\n```\n\t\u003cItemGroup\u003e\n\t  \u003cPackageReference Include=\"RhoMicro.Unions.Attributes\" Version=\"1.0.0\" /\u003e\n\t  \u003cPackageReference Include=\"RhoMicro.Unions\" Version=\"1.0.0\"\u003e\n\t    \u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n\t    \u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n\t  \u003c/PackageReference\u003e\n\t\u003c/ItemGroup\u003e\n```\nCLI:\n```\ndotnet add package RhoMicro.Unions.Attributes\ndotnet add package RhoMicro.Unions\n```\n\n## How To Use\n\nAnnotate your union type with the `UnionType` attribute:\n```cs\n[UnionType(typeof(String))]\n[UnionType(typeof(Double))]\nreadonly partial struct Union;\n```\n\nUse your union type:\n```cs\nUnion u = \"Hello, World!\"; //implicitly converted\nu = 32; //implicitly converted\nu = false; //CS0029\tCannot implicitly convert type 'bool' to 'Union'\n```\n\n### Available attributes and instructions:\n\n#### `UnionTypeAttribute`\n\n- `representableType`: Instruct the generator on the kind of representable type:\n```cs\n[UnionType(representableType: typeof(String))]\n[UnionType(representableType: typeof(Double))]\nreadonly partial struct Union;\n```\n\n- `genericRepresentableTypeName`: use `nameof(T)` to use generic parameters as representable types (this will likely change in the future):\n```cs\n[UnionType(genericRepresentableTypeName:nameof(T))]\nreadonly partial struct Result\u003cT\u003e;\n```\n\n- `Alias`: define aliae for generated members, e.g.: \n```cs\nNames n = \"John\";\nif(n.IsSingleName)\n{\n    //...\n} else if(n.IsMultipleNames)\n{\n    //...\n}\n[UnionType(typeof(List\u003cString\u003e), Alias = \"MultipleNames\")]\n[UnionType(typeof(String), Alias = \"SingleName\")]\nreadonly partial struct Names;\n```\n\n- `Options`: define miscellaneous behaviour for the represented type:\n```cs\n/*\nInstructs the generator to emit a superset conversion operator implementation even\nthe representable type is a generic type parameter. By default, it is omitted because of possible\nunification for certain generic arguments.\n*/\n[UnionType(genericRepresentableTypeName:nameof(T), Alias = \"Result\", Options = UnionTypeOptions.SupersetOfParameter)]\nreadonly partial struct Result\u003cT\u003e;\n```\n```cs\n/*\nInstructs the generator to emit a superset conversion operator implementation even\nthe representable type is a generic type parameter. By default, it is omitted because of possible\nunification for certain generic arguments.\n*/\n[UnionType(typeof(Int32), Options = UnionTypeOptions.ImplicitConversionIfSolitary)]\nreadonly partial struct Union;\n```\n\n- `Storage`: optimize the generated storage implementation for the representable type against boxing or size constraints:\n\u003cdetails\u003e\n\u003csummary\u003e\nAvailable Storage Options: Auto, Reference, Value, Field\n\u003c/summary\u003e\n\n```cs\npublic enum StorageOption\n{\n    // The generator will automatically decide on a storage strategy.\n\n    // If the representable type is known to be a value type,\n    // this will store values of that type inside a shared value type container.\n    // Boxing will not occur.\n\n    // If the representable type is known to be a reference type,\n    // this will store values of that type inside a shared reference type container.\n\n    // If the representable type is neither known to be a reference type\n    // nor a value type, this option will cause values of that type to \n    // be stored inside a shared reference type container.\n    // If the representable type is a generic type parameter,\n    // boxing will occur for value type arguments to that parameter.\n    Auto,\n\n    // The generator will always store values of the representable type\n    // inside a shared reference type container.\n\n    // If the representable type is known to be a value type,\n    // boxing will occur.\n\n    // If the representable type is a generic type parameter,\n    // boxing will occur for value type arguments to that parameter.\n    Reference,\n\n    // The generator will attempt to store values of the representable type\n    // inside a value type container.\n\n    // If the representable type is known to be a value type,\n    // this will store values of that type inside a shared value type container.\n    // Boxing will not occur.\n\n    // If the representable type is known to be a reference type,\n    // this will store values of that type inside a shared reference type container.\n    // Boxing will not occur.\n\n    // If the representable type is neither known to be a reference type\n    // nor a value type, this option will cause values of that type to \n    // be stored inside a shared value type container.\n    // If the representable type is a generic type parameter,\n    // an exception of type TypeLoadException will occur for\n    // reference type arguments to that parameter.\n    Value,\n\n    // The generator will attempt to store values of the representable type\n    // inside a dedicated container for that type.\n\n    // If the representable type is known to be a value type,\n    // this will store values of that type inside a dedicated \n    // value type container.\n    // Boxing will not occur.\n\n    // If the representable type is known to be a reference type,\n    // this will store values of that type inside a \n    // dedicated reference type container.\n\n    // If the representable type is neither known to be a reference type\n    // nor a value type, this option will cause values of that type to \n    // be stored inside a dedicated strongly typed container.\n    // Boxing will not occur.\n    Field\n}\n```\n\n\u003c/details\u003e\n\n#### `UnionTypeSettingsAttribute`\n\nThis attribute may target either a union type or an assembly. When targeting a union type, it defines settings specific to that type. If, however, the attribute is annotating an assembly, it supplies the default settings for every union type in that assembly.\n\n- `ConstructorAccessibility`: define the accessibility of generated constructors:\n```cs\npublic enum ConstructorAccessibilitySetting\n{\n    // Generated constructors should always be private, unless\n    // no conversion operators are generated for the type they\n    // accept. This would be the case for interface types or\n    // supertypes of the target union.\n    PublicIfInconvertible,\n    // Generated constructors should always be private.\n    Private,\n    // Generated constructors should always be public\n    Public\n}\n```\n\n- `DiagnosticsLevel`: define the reporting of diagnostics:\n```cs\n[Flags]\npublic enum DiagnosticsLevelSettings\n{\n    // Instructs the analyzer to report info diagnostics.\n    Info = 0x01,\n    // Instructs the analyzer to report warning diagnostics.\n    Warning = 0x02,\n    // Instructs the analyzer to report error diagnostics.\n    Error = 0x04,\n    // Instructs the analyzer to report all diagnostics.\n    All = Info + Warning + Error\n}\n```\n\n- `ToStringSetting`: define how implementations of `ToString` should be generated:\n```cs\npublic enum ToStringSetting\n{\n    // The generator will emit an implementation that returns detailed information, including:\n    // - the name of the union type\n    // - a list of types representable by the union type\n    // - an indication of which type is being represented by the instance\n    // - the value currently being represented by the instance\n    Detailed,\n    // The generator will not generate an implementation of ToString.\n    None,\n    // The generator will generate an implementation that returns the result of\n    // calling ToString on the currently represented value.\n    Simple\n}\n```\n\n- `Layout`: generate a layout attribute for size optimization\n```cs\npublic enum LayoutSetting\n{\n    // Generate an annotation optimized for size.\n    Small,\n    // Do not generate any annotations.\n    Auto\n}\n```\n\n\n- Generic Names: define how generic type parameter names should be generated:\n```cs\n// Gets or sets the name of the generic parameter for generic Is, As and factory methods. \n// Set this property in order to avoid name collisions with generic union type parameters\npublic String GenericTValueName { get; set; }\n// Gets or sets the name of the generic parameter for the DownCast method. \n// Set this property in order to avoid name collisions with generic union type parameters\npublic String DowncastTypeName { get; set; }\n// Gets or sets the name of the generic parameter for the Match method. \n// Set this property in order to avoid name collisions with generic union type parameters\npublic String MatchTypeName { get; set; }\n```\n\n#### `RelationAttribute`\n\nThis attribute defines a relation between the targeted union type the supplied type. The following relations are available:\n- `None`\n- `Congruent`\n- `Superset`\n- `Subset`\n- `Intersection`\n\nThe generator will automatically detect the relation between two union types. The only requirement is for one of the two types to be annotated with the `RelationAttribute`:\n```cs\n[UnionType(typeof(DateTime))]\n[UnionType(typeof(String))]\n[UnionType(typeof(Double))]\n[Relation(typeof(CongruentUnion))]\n[Relation(typeof(SubsetUnion))]\n[Relation(typeof(SupersetUnion))]\n[Relation(typeof(IntersectionUnion))]\nreadonly partial struct Union;\n\n[UnionType(typeof(Double))]\n[UnionType(typeof(DateTime))]\n[UnionType(typeof(String))]\nsealed partial class CongruentUnion;\n\n[UnionType(typeof(DateTime))]\n[UnionType(typeof(String))]\npartial class SubsetUnion;\n\n[UnionType(typeof(DateTime))]\n[UnionType(typeof(String))]\n[UnionType(typeof(Double))]\n[UnionType(typeof(Int32))]\npartial struct SupersetUnion;\n\n[UnionType(typeof(Int16))]\n[UnionType(typeof(String))]\n[UnionType(typeof(Double))]\n[UnionType(typeof(List\u003cByte\u003e))]\npartial class IntersectionUnion;\n```\n\n## Contrived Example\n\nIn our imaginary usecase, a user shall be retrieved from the infrastructure via a name query. The following types will be found throughout the example:\n\n```cs\nsealed record User(String Name);\n\nenum ErrorCode\n{\n    NotFound,\n    Unauthorized\n}\n\nreadonly record struct MultipleUsersError(Int32 Count);\n```\n\nThe `User` type represents a user. The `ErrorCode` represents an error that does not contain additional information, like `MultipleUsersError` does. It represents multiple users having been found while only one was requested.\n\nWe define a union type to represent our imaginary query:\n\n```cs\n[UnionType(typeof(ErrorCode))]\n[UnionType(typeof(MultipleUsersError))]\n[UnionType(typeof(User))]\nreadonly partial struct GetUserResult;\n```\nInstances of `GetUserResult` can represent *either* an instance of `ErrorCode`, `MultipleUsersError` or `User`.\n\nIt will be used in a service façade like so:\n```cs\ninterface IUserService\n{\n    GetUserResult GetUserByName(String name);\n}\n```\nA repository abstracts over the underlying infrastructure:\n```cs\ninterface IUserRepository\n{\n    IQueryable\u003cUser\u003e UsersByName(String name);\n}\n```\nAccess violations would be communicated through the repository using the following exception type: \n```cs\nsealed class UnauthorizedDatabaseAccessException : Exception;\n```\nAn implementation of the `IUserService` is provided as follows:\n```cs\nsealed class UserService : IUserService\n{\n    public UserService(IUserRepository repository) =\u003e _repository = repository;\n\n    private readonly IUserRepository _repository;\n\n    public GetUserResult GetUserByName(String name)\n    {\n        IQueryable\u003cUser\u003e users;\n        try\n        {\n            users = _repository.UsersByName(name);\n        } catch(UnauthorizedDatabaseAccessException)\n        {\n            return ErrorCode.Unauthorized;\n        }\n\n        var reifiedUsers = users.ToArray();\n        if(reifiedUsers.Length == 0)\n        {\n            return ErrorCode.NotFound;\n        } else if(reifiedUsers.Length \u003e 1)\n        {\n            return new MultipleUsersError(reifiedUsers.Length);\n        }\n\n        return reifiedUsers[0];\n    }\n}\n```\nAs you can see, possible representations of `GetUserResult` are implicitly converted and returned by the service. Users of `OneOf` will be familiar with this.\n\nOn the consumer side of this api, a generated `Match` function helps with transforming the union instance to another type:\n\n```cs\nsealed class UserModel\n{\n    public UserModel(IUserService service) =\u003e _service = service;\n\n    private readonly IUserService _service;\n\n    public String ErrorMessage { get; private set; } = String.Empty;\n    public User? User { get; private set; }\n    public void SetUser(String name)\n    {\n        var getUserResult = _service.GetUserByName(name);\n        User = getUserResult.Match(\n            HandleErrorCode,\n            HandleMultipleResult,\n            user =\u003e user);\n    }\n    private User? HandleErrorCode(ErrorCode code)\n    {\n        ErrorMessage = code switch\n        {\n            ErrorCode.NotFound =\u003e \"The user could not be located.\",\n            ErrorCode.Unauthorized =\u003e \"You are not authorized to access users.\",\n            _ =\u003e throw new NotImplementedException()\n        };\n        return null;\n    }\n    private User? HandleMultipleResult(MultipleUsersError result)\n    {\n        ErrorMessage = $\"{result.Count} users have been located. The name was not precise enough.\";\n        return null;\n    }\n}\n```\n\nHere is a list of some generated members on the `GetUserResult` union type (implementations have been elided):\n```cs\n/*Factories*/\npublic static GetUserResult Create(ErrorCode value);\npublic static GetUserResult Create(MultipleUsersResult value);\npublic static GetUserResult Create(User value);\npublic static Boolean TryCreate\u003cTValue\u003e(TValue value, out GetUserResult instance);\npublic static GetUserResult Create\u003cTValue\u003e(TValue value);\n\n/*Handling Methods*/\npublic void Switch(\n    Action\u003cErrorCode\u003e onErrorCode, \n    Action\u003cMultipleUsersResult\u003e onMultipleUsersResult,\n    Action\u003cUser\u003e onUser);\n\npublic TResult Match\u003cTResult\u003e(\n    Func\u003cErrorCode, TResult\u003e onErrorCode,\n    Func\u003cMultipleUsersResult, TResult\u003e onMultipleUsersResult,\n    Func\u003cUser, TResult\u003e onUser);\n\n/*Casting \u0026 Conversion*/\npublic TResult DownCast\u003cTResult\u003e();\n\npublic Boolean Is\u003cTValue\u003e();\n\npublic Boolean Is(Type type);\n\npublic TValue As\u003cTValue\u003e();\n\npublic Boolean IsErrorCode;\npublic ErrorCode AsErrorCode;\n\npublic Boolean IsMultipleUsersResult;\npublic MultipleUsersResult AsMultipleUsersResult;\n\npublic Boolean IsUser;\npublic User AsUser;\n\npublic Type GetRepresentedType();\n\npublic static implicit operator GetUserResult(ErrorCode value);\npublic static explicit operator ErrorCode(GetUserResult union);\n\npublic static implicit operator GetUserResult(MultipleUsersResult value);\npublic static explicit operator MultipleUsersResult(GetUserResult union);\n\npublic static implicit operator GetUserResult(User value);\npublic static explicit operator User(GetUserResult union);\n```\n\n## Why Not OneOf?\n\nHere are some issues that I have with `OneOf` that this generator aims to solve:\n- the internal tag field is a byte instead of an int\n- by default, not every representable type has a dedicated field generated for it\n- generic value type unions are possible:\n```cs\n[UnionType(typeof(String))]\n[UnionType(nameof(T), Alias = \"Result\")]\nreadonly partial struct Result\u003cT\u003e;\n```\nwith helpful members like `IsResult` being generated for you.\n- type order does not matter; convert to equivalent unions with ease:\n```cs\nUnion u = DateTime.Now;\n//Output: Union(\u003cDateTime\u003e | Double | String){23/11/2023 17:58:58}\nConsole.WriteLine(u);\nEquivalentUnion eu = u.DownCast\u003cEquivalentUnion\u003e();\n//Output: EquivalentUnion(\u003cDateTime\u003e | Double | String){23/11/2023 17:58:58}\nConsole.WriteLine(eu);\n\n[UnionType(typeof(DateTime))]\n[UnionType(typeof(String))]\n[UnionType(typeof(Double))]\nreadonly partial struct Union;\n\n[UnionType(typeof(Double))]\n[UnionType(typeof(DateTime))]\n[UnionType(typeof(String))]\nsealed partial class EquivalentUnion;\n```\n- avoid `OneOf` invading your apis, instead rely on dedicated domain names for your union types\n- use custom generated members with meaningful names for access to union data:\n```cs\nvar r = Result\u003cString\u003e.CreateFromResult(\"Hello, World!\");\nif(r.IsErrorMessage)\n{\n    //handle error\n} else if(r.IsResult)\n{\n    //handle result\n}\n\n//alternatively:\nr.Switch(\n    onErrorMessage: m =\u003e/*handle error*/,\n    onResult: r =\u003e/*handle result*/);\n\n[UnionType(typeof(String), Alias = \"ErrorMessage\")]\n[UnionType(nameof(T), Alias = \"Result\")]\nreadonly partial struct Result\u003cT\u003e;\n```\n*Note that for generic union types, there will be no conversion operators generated for representable parameter types. That is why we are using the generated `CreateFromResult` factory method here.*\n","funding_links":[],"categories":["Source Generators"],"sub_categories":["Functional Programming"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSleepWellPupper%2FUnions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSleepWellPupper%2FUnions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSleepWellPupper%2FUnions/lists"}