├── .codecov.yml ├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── README.md ├── dub.sdl ├── meson.build ├── source └── taggedalgebraic │ ├── meson.build │ ├── package.d │ ├── taggedalgebraic.d │ ├── taggedunion.d │ └── visit.d └── travis.sh /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Documentation: https://docs.codecov.io/docs/codecov-yaml 2 | 3 | coverage: 4 | precision: 3 5 | round: down 6 | range: "80...100" 7 | 8 | status: 9 | # Learn more at https://docs.codecov.io/docs/commit-status 10 | project: true 11 | patch: true 12 | changes: true 13 | 14 | comment: false 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h,d,di,dd,json}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test Suite 2 | 3 | # Only triggers on pushes/PRs to master 4 | on: 5 | pull_request: 6 | branches: 7 | - master 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | test: 14 | name: CI 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] 19 | dc: [dmd-latest, ldc-latest, ldc-1.15.0] 20 | arch: [x86, x86_64] 21 | 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Install D compiler 27 | uses: dlang-community/setup-dlang@v1 28 | with: 29 | compiler: ${{ matrix.dc }} 30 | 31 | - name: Run tests 32 | env: 33 | CONFIG: ${{matrix.config}} 34 | ARCH: ${{matrix.arch}} 35 | shell: bash 36 | run: dub test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.o 3 | *.obj 4 | 5 | # Compiled Dynamic libraries 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Compiled Static libraries 11 | *.a 12 | *.lib 13 | 14 | # Executables 15 | *.exe 16 | build/ 17 | 18 | # DUB 19 | .dub 20 | docs.json 21 | __dummy.html 22 | docs/ 23 | 24 | # DUB testing artifacts 25 | *-test-library 26 | *-test-application 27 | 28 | # Code coverage 29 | *.lst 30 | 31 | # Emacs 32 | *~ 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | # Order: oldest DMD, newest LDC, oldest LDC 8 | # Latest DMD is in the matrix.include to give it the `COVERAGE` env variable 9 | d: 10 | - dmd-2.085.1 11 | - ldc-1.19.0 12 | - ldc-1.15.0 13 | - dmd-nightly 14 | - ldc-latest-ci 15 | 16 | env: 17 | global: 18 | - TEST_MESON=true 19 | 20 | before_install: 21 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get update && sudo apt-get -y install python3-pip python3-setuptools; fi 22 | - pip3 install meson ninja 23 | 24 | matrix: 25 | include: 26 | # Last FE GDC is compatible with 27 | - d: dmd-2.076.1 28 | os: linux 29 | script: dub build 30 | - d: dmd-2.076.1 31 | os: osx 32 | script: dub build 33 | - d: dmd-2.090.0 34 | os: linux 35 | env: [COVERAGE=true] 36 | - d: dmd-2.090.0 37 | os: osx 38 | env: [COVERAGE=true] 39 | allow_failures: 40 | - d: dmd-nightly 41 | - d: ldc-latest-ci 42 | 43 | script: ./travis.sh 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TaggedAlgebraic 2 | =============== 3 | 4 | Implementation of a generic `TaggedUnion` type along with a `TaggedAlgebraic` type that forwards all methods and operators of the contained types using dynamic dispatch. 5 | 6 | [![Build Status](https://travis-ci.com/s-ludwig/taggedalgebraic.svg?branch=master)](https://travis-ci.com/s-ludwig/taggedalgebraic) [![codecov](https://codecov.io/gh/s-ludwig/taggedalgebraic/branch/master/graph/badge.svg)](https://codecov.io/gh/s-ludwig/taggedalgebraic) 7 | 8 | API Documentation: 9 | - [`taggedalgebraic`](https://vibed.org/api/taggedalgebraic.taggedalgebraic/) 10 | - [`taggedunion`](https://vibed.org/api/taggedalgebraic.taggedunion/) 11 | 12 | 13 | Usage of `TaggedUnion` 14 | ---------------------- 15 | 16 | ```d 17 | import taggedalgebraic; 18 | 19 | struct Foo { 20 | string name; 21 | void bar() {} 22 | } 23 | 24 | union Base { 25 | int count; 26 | int offset; 27 | string str; 28 | Foo foo; 29 | } 30 | 31 | alias TUnion = TaggedUnion!Base; 32 | 33 | // Instantiate 34 | TUnion taggedInt = TUnion.count(5); 35 | TUnion taggedString = TUnion.str("Hello"); 36 | TUnion taggedFoo = TUnion.foo; 37 | TUnion taggedAny = taggedInt; 38 | taggedAny = taggedString; 39 | taggedAny = taggedFoo; 40 | 41 | // Default initializes to the first field 42 | TUnion taggedDef; 43 | assert(taggedDef.isCount); 44 | assert(taggedDef.countValue == int.init); 45 | 46 | // Check type: TUnion.Kind is an enum 47 | assert(taggedInt.kind == TUnion.Kind.count); 48 | assert(taggedString.kind == TUnion.Kind.str); 49 | assert(taggedFoo.kind == TUnion.Kind.foo); 50 | assert(taggedAny.kind == TUnion.Kind.foo); 51 | 52 | // A shorter syntax is also available 53 | assert(taggedInt.isCount); 54 | assert(!taggedInt.isOffset); 55 | assert(taggedString.isStr); 56 | assert(taggedFoo.isFoo); 57 | assert(taggedAny.isFoo); 58 | 59 | // Set to a different type 60 | taggedAny.setStr("bar"); 61 | assert(taggedAny.isStr); 62 | assert(taggedAny.strValue == "bar"); 63 | 64 | // Modify contained value by reference 65 | taggedAny.strValue = "baz"; 66 | assert(taggedAny.strValue == "baz"); 67 | 68 | // In addition to the getter, the contained value can be extracted using get!() 69 | // or by casting 70 | assert(taggedInt.value!(TUnion.Kind.count) == 5); 71 | assert(taggedInt.value!int == 5); 72 | assert(cast(byte)taggedInt == 5); 73 | 74 | // Multiple kinds of the same type are supported 75 | taggedAny.setOffset(5); 76 | assert(taggedAny.isOffset); 77 | assert(!taggedAny.isCount); 78 | 79 | // Unique types can also be set directly 80 | taggedAny = "foo"; 81 | assert(taggedAny.isStr); 82 | taggedAny = TUnion(Foo.init); 83 | assert(taggedAny.isFoo); 84 | ``` 85 | 86 | 87 | Usage of `TaggedAlgebraic` 88 | -------------------------- 89 | 90 | ```d 91 | import taggedalgebraic; 92 | 93 | struct Foo { 94 | string name; 95 | void bar() {} 96 | } 97 | 98 | union Base { 99 | int i; 100 | string str; 101 | Foo foo; 102 | } 103 | 104 | alias TAlgebraic = TaggedAlgebraic!Base; 105 | 106 | // Instantiate 107 | TAlgebraic taggedInt = 5; 108 | TAlgebraic taggedString = "Hello"; 109 | TAlgebraic taggedFoo = Foo(); 110 | TAlgebraic taggedAny = taggedInt; 111 | taggedAny = taggedString; 112 | taggedAny = taggedFoo; 113 | 114 | // Check type: TAlgebraic.Kind is an enum 115 | assert(taggedInt.kind == TAlgebraic.Kind.i); 116 | assert(taggedString.kind == TAlgebraic.Kind.str); 117 | assert(taggedFoo.kind == TAlgebraic.Kind.foo); 118 | assert(taggedAny.kind == TAlgebraic.Kind.foo); 119 | 120 | // In most cases, can simply use as-is 121 | auto num = 4 + taggedInt; 122 | auto msg = taggedString ~ " World!"; 123 | taggedFoo.bar(); 124 | if (taggedAny.kind == TAlgebraic.Kind.foo) // Make sure to check type first! 125 | taggedAny.bar(); 126 | //taggedString.bar(); // AssertError: Not a Foo! 127 | 128 | // Convert back by casting 129 | auto i = cast(int) taggedInt; 130 | auto str = cast(string) taggedString; 131 | auto foo = cast(Foo) taggedFoo; 132 | if (taggedAny.kind == TAlgebraic.Kind.foo) // Make sure to check type first! 133 | auto foo2 = cast(Foo) taggedAny; 134 | //cast(Foo) taggedString; // AssertError! 135 | 136 | // Kind is an enum, so final switch is supported: 137 | final switch (taggedAny.kind) { 138 | case TAlgebraic.Kind.i: 139 | // It's "int i" 140 | break; 141 | 142 | case TAlgebraic.Kind.str: 143 | // It's "string str" 144 | break; 145 | 146 | case TAlgebraic.Kind.foo: 147 | // It's "Foo foo" 148 | break; 149 | } 150 | ``` 151 | 152 | Compiler support 153 | ---------------- 154 | 155 | The library is tested to work on the following compilers: 156 | 157 | - DMD 2.076.1 up to 2.088.0 158 | - LDC 1.6.0 up to 1.17.0 159 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "taggedalgebraic" 2 | description `A "tagged union" implementation with transparent operator forwarding.` 3 | authors "Sönke Ludwig" 4 | copyright "Copyright © 2015, Sönke Ludiwg" 5 | license "BSL-1.0" 6 | 7 | buildType "unittest" { 8 | buildOptions "unittests" "debugMode" "debugInfo" 9 | dflags "-preview=dip1000" 10 | } 11 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('taggedalgebraic', 'd', 2 | meson_version: '>=0.54', 3 | version: '0.11.20' 4 | ) 5 | 6 | project_soversion = '0' 7 | project_version_suffix = '' 8 | project_version = meson.project_version() 9 | project_version_full = project_version + project_version_suffix 10 | 11 | source_root = meson.source_root() 12 | build_root = meson.build_root() 13 | subdir('source/taggedalgebraic') 14 | 15 | taggedalgebraic_dep = declare_dependency( 16 | include_directories: include_directories('source'), 17 | link_with: taggedalgebraic_lib 18 | ) 19 | 20 | taggedalgebraic_source_dep = declare_dependency( 21 | version: project_version, 22 | include_directories: '../' 23 | ) 24 | 25 | meson.override_dependency('taggedalgebraic', taggedalgebraic_dep) 26 | -------------------------------------------------------------------------------- /source/taggedalgebraic/meson.build: -------------------------------------------------------------------------------- 1 | taggedalgebraic_src = [ 2 | 'package.d', 3 | 'taggedalgebraic.d', 4 | 'taggedunion.d', 5 | 'visit.d', 6 | ] 7 | 8 | # https://github.com/mesonbuild/meson/issues/6862 9 | if build_machine.system() == 'darwin' 10 | taggedalgebraic_lib = library( 11 | 'taggedalgebraic', 12 | taggedalgebraic_src, 13 | install: true, 14 | include_directories: include_directories('../'), 15 | ) 16 | else 17 | taggedalgebraic_lib = library( 18 | 'taggedalgebraic', 19 | taggedalgebraic_src, 20 | install: true, 21 | include_directories: include_directories('../'), 22 | version: project_version, 23 | ) 24 | endif 25 | 26 | pkgc = import('pkgconfig') 27 | 28 | pkgc.generate( 29 | taggedalgebraic_lib, 30 | subdirs: 'd/taggedalgebraic', 31 | ) 32 | 33 | install_headers( 34 | taggedalgebraic_src, 35 | subdir: 'd/taggedalgebraic/taggedalgebraic', 36 | ) 37 | 38 | test_exe = executable( 39 | 'taggedalgebraic_test', 40 | taggedalgebraic_src, 41 | include_directories: include_directories('../'), 42 | d_unittest: true, 43 | link_args: '-main', 44 | ) 45 | 46 | test('test_taggedalgebraic', test_exe) 47 | -------------------------------------------------------------------------------- /source/taggedalgebraic/package.d: -------------------------------------------------------------------------------- 1 | module taggedalgebraic; 2 | 3 | public import taggedalgebraic.taggedalgebraic; 4 | public import taggedalgebraic.taggedunion; 5 | public import taggedalgebraic.visit; 6 | -------------------------------------------------------------------------------- /source/taggedalgebraic/taggedalgebraic.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Algebraic data type implementation based on a tagged union. 3 | * 4 | * Copyright: Copyright 2015-2019, Sönke Ludwig. 5 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 | * Authors: Sönke Ludwig 7 | */ 8 | module taggedalgebraic.taggedalgebraic; 9 | 10 | public import taggedalgebraic.taggedunion; 11 | 12 | import std.algorithm.mutation : move, swap; 13 | import std.meta; 14 | import std.traits : EnumMembers, FieldNameTuple, Unqual, isInstanceOf; 15 | 16 | // TODO: 17 | // - distinguish between @property and non@-property methods. 18 | // - verify that static methods are handled properly 19 | 20 | 21 | /** Converts a given `TaggedUnion` to a `TaggedAlgebraic`. 22 | 23 | This allows to access members or operators of a `TaggedUnion` with a concise 24 | syntax. 25 | */ 26 | auto algebraic(TU)(TU tagged_union) 27 | if (isInstanceOf!(TaggedUnion, TU)) 28 | { 29 | TaggedAlgebraic!(TU.FieldDefinitionType) ret; 30 | ret.m_union = tagged_union; 31 | return ret; 32 | } 33 | 34 | /// 35 | unittest { 36 | import taggedalgebraic.visit : visit; 37 | 38 | struct Button { 39 | string caption; 40 | int callbackID; 41 | } 42 | 43 | struct Label { 44 | string caption; 45 | } 46 | 47 | union U { 48 | Button button; 49 | Label label; 50 | } 51 | 52 | alias Control = TaggedUnion!U; 53 | 54 | // define a generic list of controls 55 | Control[] controls = [ 56 | Control(Button("Hello", -1)), 57 | Control(Label("World")) 58 | ]; 59 | 60 | // just a dummy for the sake of the example 61 | void print(string message) {} 62 | 63 | // short syntax using `algebraic` 64 | foreach (c; controls) 65 | print(algebraic(c).caption); 66 | 67 | // slightly longer and noisier alternative using `visit` 68 | foreach (c; controls) 69 | print(c.visit!(ct => ct.caption)); 70 | 71 | // low level alternative 72 | foreach (c; controls) { 73 | final switch (c.kind) { 74 | case Control.Kind.button: 75 | print(c.buttonValue.caption); 76 | break; 77 | case Control.Kind.label: 78 | print(c.labelValue.caption); 79 | break; 80 | } 81 | } 82 | } 83 | 84 | 85 | /** Implements a generic algebraic type using an enum to identify the stored type. 86 | 87 | This struct takes a `union` or `struct` declaration as an input and builds 88 | an algebraic data type from its fields, using an automatically generated 89 | `Kind` enumeration to identify which field of the union is currently used. 90 | Multiple fields with the same value are supported. 91 | 92 | All operators and methods are transparently forwarded to the contained 93 | value. The caller has to make sure that the contained value supports the 94 | requested operation. Failure to do so will result in an assertion failure. 95 | 96 | The return value of forwarded operations is determined as follows: 97 | $(UL 98 | $(LI If the type can be uniquely determined, it is used as the return 99 | value) 100 | $(LI If there are multiple possible return values and all of them match 101 | the unique types defined in the `TaggedAlgebraic`, a 102 | `TaggedAlgebraic` is returned.) 103 | $(LI If there are multiple return values and none of them is a 104 | `Variant`, an `Algebraic` of the set of possible return types is 105 | returned.) 106 | $(LI If any of the possible operations returns a `Variant`, this is used 107 | as the return value.) 108 | ) 109 | */ 110 | struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct) || is(U == enum)) 111 | { 112 | import std.algorithm : among; 113 | import std.string : format; 114 | 115 | /// Alias of the type used for defining the possible storage types/kinds. 116 | deprecated alias Union = U; 117 | 118 | private alias FieldDefinitionType = U; 119 | 120 | /// The underlying tagged union type 121 | alias UnionType = TaggedUnion!U; 122 | 123 | private TaggedUnion!U m_union; 124 | 125 | /// A type enum that identifies the type of value currently stored. 126 | alias Kind = UnionType.Kind; 127 | 128 | /// Compatibility alias 129 | deprecated("Use 'Kind' instead.") alias Type = Kind; 130 | 131 | /// The type ID of the currently stored value. 132 | @property Kind kind() const { return m_union.kind; } 133 | 134 | // Compatibility alias 135 | deprecated("Use 'kind' instead.") 136 | alias typeID = kind; 137 | 138 | // constructors 139 | //pragma(msg, generateConstructors!U()); 140 | mixin(generateConstructors!U); 141 | 142 | this(TaggedAlgebraic other) 143 | { 144 | rawSwap(this, other); 145 | } 146 | 147 | void opAssign(TaggedAlgebraic other) 148 | { 149 | rawSwap(this, other); 150 | } 151 | 152 | /// Enables conversion or extraction of the stored value. 153 | T opCast(T)() { return cast(T)m_union; } 154 | /// ditto 155 | T opCast(T)() const { return cast(T)m_union; } 156 | 157 | /// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value. 158 | string toString() const { return cast(string)this; } 159 | 160 | // NOTE: "this TA" is used here as the functional equivalent of inout, 161 | // just that it generates one template instantiation per modifier 162 | // combination, so that we can actually decide what to do for each 163 | // case. 164 | 165 | /// Enables the access to methods and propeties/fields of the stored value. 166 | template opDispatch(string name) 167 | if (hasAnyMember!(TaggedAlgebraic, name)) 168 | { 169 | /// Enables the invocation of methods of the stored value. 170 | auto ref opDispatch(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); } 171 | /// Enables accessing properties/fields of the stored value. 172 | @property auto ref opDispatch(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); } 173 | } 174 | 175 | static if (is(typeof(m_union.toHash()))) { 176 | size_t toHash() 177 | const @safe nothrow { 178 | return m_union.toHash(); 179 | } 180 | } 181 | 182 | /// Enables equality comparison with the stored value. 183 | auto ref opEquals(T, this TA)(auto ref T other) 184 | if (is(Unqual!T == TaggedAlgebraic) || hasOp!(TA, OpKind.binary, "==", T)) 185 | { 186 | static if (is(Unqual!T == TaggedAlgebraic)) { 187 | return m_union == other.m_union; 188 | } else return implementOp!(OpKind.binary, "==")(this, other); 189 | } 190 | /// Enables relational comparisons with the stored value. 191 | auto ref opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); } 192 | /// Enables the use of unary operators with the stored value. 193 | auto ref opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); } 194 | /// Enables the use of binary operators with the stored value. 195 | auto ref opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); } 196 | /// Enables the use of binary operators with the stored value. 197 | auto ref opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T) && !isInstanceOf!(TaggedAlgebraic, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); } 198 | /// ditto 199 | auto ref opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T) && isInstanceOf!(TaggedAlgebraic, T) && !hasOp!(T, OpKind.opBinary, op, TA)) { return implementOp!(OpKind.binaryRight, op)(this, other); } 200 | /// Enables operator assignments on the stored value. 201 | auto ref opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); } 202 | /// Enables indexing operations on the stored value. 203 | auto ref opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); } 204 | /// Enables index assignments on the stored value. 205 | auto ref opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); } 206 | /// Enables call syntax operations on the stored value. 207 | auto ref opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); } 208 | } 209 | 210 | /// 211 | @safe unittest 212 | { 213 | import taggedalgebraic.taggedalgebraic; 214 | 215 | struct Foo { 216 | string name; 217 | void bar() @safe {} 218 | } 219 | 220 | union Base { 221 | int i; 222 | string str; 223 | Foo foo; 224 | } 225 | 226 | alias Tagged = TaggedAlgebraic!Base; 227 | 228 | // Instantiate 229 | Tagged taggedInt = 5; 230 | Tagged taggedString = "Hello"; 231 | Tagged taggedFoo = Foo(); 232 | Tagged taggedAny = taggedInt; 233 | taggedAny = taggedString; 234 | taggedAny = taggedFoo; 235 | 236 | // Check type: Tagged.Kind is an enum 237 | assert(taggedInt.kind == Tagged.Kind.i); 238 | assert(taggedString.kind == Tagged.Kind.str); 239 | assert(taggedFoo.kind == Tagged.Kind.foo); 240 | assert(taggedAny.kind == Tagged.Kind.foo); 241 | 242 | // In most cases, can simply use as-is 243 | auto num = 4 + taggedInt; 244 | auto msg = taggedString ~ " World!"; 245 | taggedFoo.bar(); 246 | if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! 247 | taggedAny.bar(); 248 | //taggedString.bar(); // AssertError: Not a Foo! 249 | 250 | // Convert back by casting 251 | auto i = cast(int) taggedInt; 252 | auto str = cast(string) taggedString; 253 | auto foo = cast(Foo) taggedFoo; 254 | if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! 255 | auto foo2 = cast(Foo) taggedAny; 256 | //cast(Foo) taggedString; // AssertError! 257 | 258 | // Kind is an enum, so final switch is supported: 259 | final switch (taggedAny.kind) { 260 | case Tagged.Kind.i: 261 | // It's "int i" 262 | break; 263 | 264 | case Tagged.Kind.str: 265 | // It's "string str" 266 | break; 267 | 268 | case Tagged.Kind.foo: 269 | // It's "Foo foo" 270 | break; 271 | } 272 | } 273 | 274 | /** Operators and methods of the contained type can be used transparently. 275 | */ 276 | @safe unittest { 277 | static struct S { 278 | int v; 279 | int test() { return v / 2; } 280 | } 281 | 282 | static union Test { 283 | typeof(null) null_; 284 | int integer; 285 | string text; 286 | string[string] dictionary; 287 | S custom; 288 | } 289 | 290 | alias TA = TaggedAlgebraic!Test; 291 | 292 | TA ta; 293 | assert(ta.kind == TA.Kind.null_); 294 | 295 | ta = 12; 296 | assert(ta.kind == TA.Kind.integer); 297 | assert(ta == 12); 298 | assert(cast(int)ta == 12); 299 | assert(cast(long)ta == 12); 300 | assert(cast(short)ta == 12); 301 | 302 | ta += 12; 303 | assert(ta == 24); 304 | assert(ta - 10 == 14); 305 | 306 | ta = ["foo" : "bar"]; 307 | assert(ta.kind == TA.Kind.dictionary); 308 | assert(ta["foo"] == "bar"); 309 | 310 | ta["foo"] = "baz"; 311 | assert(ta["foo"] == "baz"); 312 | 313 | ta = S(8); 314 | assert(ta.test() == 4); 315 | } 316 | 317 | unittest { // std.conv integration 318 | import std.conv : to; 319 | 320 | static struct S { 321 | int v; 322 | int test() { return v / 2; } 323 | } 324 | 325 | static union Test { 326 | typeof(null) null_; 327 | int number; 328 | string text; 329 | } 330 | 331 | alias TA = TaggedAlgebraic!Test; 332 | 333 | TA ta; 334 | assert(ta.kind == TA.Kind.null_); 335 | ta = "34"; 336 | assert(ta == "34"); 337 | assert(to!int(ta) == 34, to!string(to!int(ta))); 338 | assert(to!string(ta) == "34", to!string(ta)); 339 | } 340 | 341 | /** Multiple fields are allowed to have the same type, in which case the type 342 | ID enum is used to disambiguate. 343 | */ 344 | @safe unittest { 345 | static union Test { 346 | typeof(null) null_; 347 | int count; 348 | int difference; 349 | } 350 | 351 | alias TA = TaggedAlgebraic!Test; 352 | 353 | TA ta = TA(12, TA.Kind.count); 354 | assert(ta.kind == TA.Kind.count); 355 | assert(ta == 12); 356 | 357 | ta = null; 358 | assert(ta.kind == TA.Kind.null_); 359 | } 360 | 361 | @safe unittest { // comparison of whole TAs 362 | static union Test { 363 | typeof(null) a; 364 | typeof(null) b; 365 | Void c; 366 | Void d; 367 | int e; 368 | int f; 369 | } 370 | alias TA = TaggedAlgebraic!Test; 371 | 372 | assert(TA(null, TA.Kind.a) == TA(null, TA.Kind.a)); 373 | assert(TA(null, TA.Kind.a) != TA(null, TA.Kind.b)); 374 | assert(TA(null, TA.Kind.a) != TA(Void.init, TA.Kind.c)); 375 | assert(TA(null, TA.Kind.a) != TA(0, TA.Kind.e)); 376 | assert(TA(Void.init, TA.Kind.c) == TA(Void.init, TA.Kind.c)); 377 | assert(TA(Void.init, TA.Kind.c) != TA(Void.init, TA.Kind.d)); 378 | assert(TA(1, TA.Kind.e) == TA(1, TA.Kind.e)); 379 | assert(TA(1, TA.Kind.e) != TA(2, TA.Kind.e)); 380 | assert(TA(1, TA.Kind.e) != TA(1, TA.Kind.f)); 381 | } 382 | 383 | unittest { // self-referential types 384 | struct S { 385 | int num; 386 | TaggedAlgebraic!This[] arr; 387 | TaggedAlgebraic!This[string] obj; 388 | } 389 | alias TA = TaggedAlgebraic!S; 390 | 391 | auto ta = TA([ 392 | TA(12), 393 | TA(["bar": TA(13)]) 394 | ]); 395 | 396 | assert(ta.kind == TA.Kind.arr); 397 | assert(ta[0].kind == TA.Kind.num); 398 | assert(ta[0] == 12); 399 | assert(ta[1].kind == TA.Kind.obj); 400 | assert(ta[1]["bar"] == 13); 401 | } 402 | 403 | unittest { 404 | // test proper type modifier support 405 | static struct S { 406 | void test() {} 407 | void testI() immutable {} 408 | void testC() const {} 409 | void testS() shared {} 410 | void testSC() shared const {} 411 | } 412 | static union U { 413 | S s; 414 | } 415 | 416 | auto u = TaggedAlgebraic!U(S.init); 417 | const uc = u; 418 | immutable ui = cast(immutable)u; 419 | //const shared usc = cast(shared)u; 420 | //shared us = cast(shared)u; 421 | 422 | static assert( is(typeof(u.test()))); 423 | static assert(!is(typeof(u.testI()))); 424 | static assert( is(typeof(u.testC()))); 425 | static assert(!is(typeof(u.testS()))); 426 | static assert(!is(typeof(u.testSC()))); 427 | 428 | static assert(!is(typeof(uc.test()))); 429 | static assert(!is(typeof(uc.testI()))); 430 | static assert( is(typeof(uc.testC()))); 431 | static assert(!is(typeof(uc.testS()))); 432 | static assert(!is(typeof(uc.testSC()))); 433 | 434 | static assert(!is(typeof(ui.test()))); 435 | static assert( is(typeof(ui.testI()))); 436 | static assert( is(typeof(ui.testC()))); 437 | static assert(!is(typeof(ui.testS()))); 438 | static assert( is(typeof(ui.testSC()))); 439 | 440 | /*static assert(!is(typeof(us.test()))); 441 | static assert(!is(typeof(us.testI()))); 442 | static assert(!is(typeof(us.testC()))); 443 | static assert( is(typeof(us.testS()))); 444 | static assert( is(typeof(us.testSC()))); 445 | 446 | static assert(!is(typeof(usc.test()))); 447 | static assert(!is(typeof(usc.testI()))); 448 | static assert(!is(typeof(usc.testC()))); 449 | static assert(!is(typeof(usc.testS()))); 450 | static assert( is(typeof(usc.testSC())));*/ 451 | } 452 | 453 | unittest { 454 | // test attributes on contained values 455 | import std.typecons : Rebindable, rebindable; 456 | 457 | class C { 458 | void test() {} 459 | void testC() const {} 460 | void testI() immutable {} 461 | } 462 | union U { 463 | Rebindable!(immutable(C)) c; 464 | } 465 | 466 | auto ta = TaggedAlgebraic!U(rebindable(new immutable C)); 467 | static assert(!is(typeof(ta.test()))); 468 | static assert( is(typeof(ta.testC()))); 469 | static assert( is(typeof(ta.testI()))); 470 | } 471 | 472 | // test recursive definition using a wrapper dummy struct 473 | // (needed to avoid "no size yet for forward reference" errors) 474 | unittest { 475 | static struct TA { 476 | union U { 477 | TA[] children; 478 | int value; 479 | } 480 | TaggedAlgebraic!U u; 481 | alias u this; 482 | this(ARGS...)(ARGS args) { u = TaggedAlgebraic!U(args); } 483 | } 484 | 485 | auto ta = TA(null); 486 | ta ~= TA(0); 487 | ta ~= TA(1); 488 | ta ~= TA([TA(2)]); 489 | assert(ta[0] == 0); 490 | assert(ta[1] == 1); 491 | assert(ta[2][0] == 2); 492 | } 493 | 494 | unittest { // postblit/destructor test 495 | static struct S { 496 | static int i = 0; 497 | bool initialized = false; 498 | this(bool) { initialized = true; i++; } 499 | this(this) { if (initialized) i++; } 500 | ~this() { if (initialized) i--; } 501 | } 502 | 503 | static struct U { 504 | S s; 505 | int t; 506 | } 507 | alias TA = TaggedAlgebraic!U; 508 | { 509 | assert(S.i == 0); 510 | auto ta = TA(S(true)); 511 | assert(S.i == 1); 512 | { 513 | auto tb = ta; 514 | assert(S.i == 2); 515 | ta = tb; 516 | assert(S.i == 2); 517 | ta = 1; 518 | assert(S.i == 1); 519 | ta = S(true); 520 | assert(S.i == 2); 521 | } 522 | assert(S.i == 1); 523 | } 524 | assert(S.i == 0); 525 | 526 | static struct U2 { 527 | S a; 528 | S b; 529 | } 530 | alias TA2 = TaggedAlgebraic!U2; 531 | { 532 | auto ta2 = TA2(S(true), TA2.Kind.a); 533 | assert(S.i == 1); 534 | } 535 | assert(S.i == 0); 536 | } 537 | 538 | unittest { 539 | static struct S { 540 | union U { 541 | int i; 542 | string s; 543 | U[] a; 544 | } 545 | alias TA = TaggedAlgebraic!U; 546 | TA p; 547 | alias p this; 548 | } 549 | S s = S(S.TA("hello")); 550 | assert(cast(string)s == "hello"); 551 | } 552 | 553 | unittest { // multiple operator choices 554 | union U { 555 | int i; 556 | double d; 557 | } 558 | alias TA = TaggedAlgebraic!U; 559 | TA ta = 12; 560 | static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double 561 | assert((ta + 10).kind == TA.Kind.i); 562 | assert(ta + 10 == 22); 563 | static assert(is(typeof(ta + 10.5) == double)); 564 | assert(ta + 10.5 == 22.5); 565 | } 566 | 567 | unittest { // Binary op between two TaggedAlgebraic values 568 | union U { int i; } 569 | alias TA = TaggedAlgebraic!U; 570 | 571 | TA a = 1, b = 2; 572 | static assert(is(typeof(a + b) == int)); 573 | assert(a + b == 3); 574 | } 575 | 576 | unittest { // Ambiguous binary op between two TaggedAlgebraic values 577 | union U { int i; double d; } 578 | alias TA = TaggedAlgebraic!U; 579 | 580 | TA a = 1, b = 2; 581 | static assert(is(typeof(a + b) == TA)); 582 | assert((a + b).kind == TA.Kind.i); 583 | assert(a + b == 3); 584 | } 585 | 586 | unittest { 587 | struct S { 588 | union U { 589 | @disableIndex string str; 590 | S[] array; 591 | S[string] object; 592 | } 593 | alias TA = TaggedAlgebraic!U; 594 | TA payload; 595 | alias payload this; 596 | } 597 | 598 | S a = S(S.TA("hello")); 599 | S b = S(S.TA(["foo": a])); 600 | S c = S(S.TA([a])); 601 | assert(b["foo"] == a); 602 | assert(b["foo"] == "hello"); 603 | assert(c[0] == a); 604 | assert(c[0] == "hello"); 605 | } 606 | 607 | static if (__VERSION__ >= 2072) unittest { // default initialization 608 | struct S { 609 | int i = 42; 610 | } 611 | 612 | union U { S s; int j; } 613 | 614 | TaggedAlgebraic!U ta; 615 | assert(ta.i == 42); 616 | } 617 | 618 | unittest 619 | { 620 | union U { int[int] a; } 621 | 622 | foreach (TA; AliasSeq!(TaggedAlgebraic!U, const(TaggedAlgebraic!U))) 623 | { 624 | TA ta = [1 : 2]; 625 | assert(cast(int[int])ta == [1 : 2]); 626 | } 627 | } 628 | 629 | static if (__VERSION__ >= 2072) { 630 | unittest { // issue #8 631 | static struct Result(T,E) 632 | { 633 | static union U 634 | { 635 | T ok; 636 | E err; 637 | } 638 | alias TA = TaggedAlgebraic!U; 639 | TA payload; 640 | alias payload this; 641 | 642 | this(T ok) { payload = ok; } 643 | this(E err) { payload = err; } 644 | } 645 | 646 | static struct Option(T) 647 | { 648 | static union U 649 | { 650 | T some; 651 | typeof(null) none; 652 | } 653 | alias TA = TaggedAlgebraic!U; 654 | TA payload; 655 | alias payload this; 656 | 657 | this(T some) { payload = some; } 658 | this(typeof(null) none) { payload = null; } 659 | } 660 | 661 | Result!(Option!size_t, int) foo() 662 | { 663 | return Result!(Option!size_t, int)(42); 664 | } 665 | 666 | assert(foo() == 42); 667 | } 668 | } 669 | 670 | unittest { // issue #13 671 | struct S1 { Void dummy; int foo; } 672 | struct S { 673 | struct T { TaggedAlgebraic!S1 foo() { return TaggedAlgebraic!S1(42); } } 674 | struct U { string foo() { return "foo"; } } 675 | Void dummy; 676 | T t; 677 | U u; 678 | } 679 | alias TA = TaggedAlgebraic!S; 680 | auto ta = TA(S.T.init); 681 | assert(ta.foo().get!(TaggedAlgebraic!S1) == 42); 682 | 683 | ta = TA(S.U.init); 684 | assert(ta.foo() == "foo"); 685 | } 686 | 687 | unittest 688 | { 689 | static union U { int[] a; } 690 | TaggedAlgebraic!U ta; 691 | ta = [1,2,3]; 692 | assert(ta.length == 3); 693 | ta.length = 4; 694 | //assert(ta.length == 4); //FIXME 695 | assert(ta.opDispatch!"sizeof" == (int[]).sizeof); 696 | } 697 | 698 | 699 | /** Tests if the algebraic type stores a value of a certain data type. 700 | */ 701 | bool hasType(T, U)(const scope ref TaggedAlgebraic!U ta) 702 | { 703 | alias Fields = Filter!(fieldMatchesType!(U, T), ta.m_union.fieldNames); 704 | static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~"."); 705 | 706 | switch (ta.kind) { 707 | default: return false; 708 | foreach (i, fname; Fields) 709 | case __traits(getMember, ta.Kind, fname): 710 | return true; 711 | } 712 | assert(false); // never reached 713 | } 714 | /// ditto 715 | bool hasType(T, U)(const scope TaggedAlgebraic!U ta) 716 | { 717 | return hasType!(T, U)(ta); 718 | } 719 | 720 | /// 721 | unittest { 722 | union Fields { 723 | int number; 724 | string text; 725 | } 726 | 727 | TaggedAlgebraic!Fields ta = "test"; 728 | 729 | assert(ta.hasType!string); 730 | assert(!ta.hasType!int); 731 | 732 | ta = 42; 733 | assert(ta.hasType!int); 734 | assert(!ta.hasType!string); 735 | } 736 | 737 | unittest { // issue #1 738 | union U { 739 | int a; 740 | int b; 741 | } 742 | alias TA = TaggedAlgebraic!U; 743 | 744 | TA ta = TA(0, TA.Kind.b); 745 | static assert(!is(typeof(ta.hasType!double))); 746 | assert(ta.hasType!int); 747 | } 748 | 749 | unittest { 750 | union U { 751 | int a; 752 | float b; 753 | } 754 | alias TA = TaggedAlgebraic!U; 755 | 756 | const(TA) test() { return TA(12); } 757 | assert(test().hasType!int); 758 | } 759 | 760 | 761 | /** Gets the value stored in an algebraic type based on its data type. 762 | */ 763 | ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) 764 | { 765 | static if (is(T == TaggedUnion!U)) 766 | return ta.m_union; 767 | else return ta.m_union.value!T; 768 | } 769 | /// ditto 770 | inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta) 771 | { 772 | return ta.m_union.value!T; 773 | } 774 | 775 | @nogc @safe nothrow unittest { 776 | struct Fields { 777 | int a; 778 | float b; 779 | } 780 | alias TA = TaggedAlgebraic!Fields; 781 | auto ta = TA(1); 782 | assert(ta.get!int == 1); 783 | ta.get!int = 2; 784 | assert(ta.get!int == 2); 785 | ta = TA(1.0); 786 | assert(ta.get!float == 1.0); 787 | } 788 | 789 | /** Gets the value stored in an algebraic type based on its kind. 790 | */ 791 | ref get(alias kind, U)(ref inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind)) 792 | { 793 | return ta.m_union.value!kind; 794 | } 795 | /// ditto 796 | auto get(alias kind, U)(inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind)) 797 | { 798 | return ta.m_union.value!kind; 799 | } 800 | 801 | @nogc @safe nothrow unittest { 802 | struct Fields { 803 | int a; 804 | float b; 805 | } 806 | alias TA = TaggedAlgebraic!Fields; 807 | auto ta = TA(1); 808 | assert(ta.get!(TA.Kind.a) == 1); 809 | ta.get!(TA.Kind.a) = 2; 810 | assert(ta.get!(TA.Kind.a) == 2); 811 | ta = TA(1.0); 812 | assert(ta.get!(TA.Kind.b) == 1.0); 813 | } 814 | 815 | /** Calls a the given callback with the static type of the contained value. 816 | 817 | The `handler` callback must be a lambda or a single-argument template 818 | function that accepts all possible types that the given `TaggedAlgebraic` 819 | can hold. 820 | 821 | Returns: 822 | If `handler` has a non-void return value, its return value gets 823 | forwarded to the caller. 824 | */ 825 | auto apply(alias handler, TA)(TA ta) 826 | if (isInstanceOf!(TaggedAlgebraic, TA)) 827 | { 828 | final switch (ta.kind) { 829 | foreach (i, fn; TA.m_union.fieldNames) { 830 | case __traits(getMember, ta.Kind, fn): 831 | return handler(get!(TA.m_union.FieldTypes[i])(ta)); 832 | } 833 | } 834 | static if (__VERSION__ <= 2068) assert(false); 835 | } 836 | /// ditto 837 | auto apply(alias handler, T)(T value) 838 | if (!isInstanceOf!(TaggedAlgebraic, T)) 839 | { 840 | return handler(value); 841 | } 842 | 843 | /// 844 | unittest { 845 | union U { 846 | int i; 847 | string s; 848 | } 849 | alias TA = TaggedAlgebraic!U; 850 | 851 | assert(TA(12).apply!((v) { 852 | static if (is(typeof(v) == int)) { 853 | assert(v == 12); 854 | return 1; 855 | } else { 856 | return 0; 857 | } 858 | }) == 1); 859 | 860 | assert(TA("foo").apply!((v) { 861 | static if (is(typeof(v) == string)) { 862 | assert(v == "foo"); 863 | return 2; 864 | } else { 865 | return 0; 866 | } 867 | }) == 2); 868 | 869 | "baz".apply!((v) { 870 | assert(v == "baz"); 871 | }); 872 | } 873 | 874 | 875 | /// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member. 876 | @property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); } 877 | 878 | private struct DisableOpAttribute { 879 | OpKind kind; 880 | string name; 881 | } 882 | 883 | /// User-defined attribute to enable only safe calls on the given member(s). 884 | enum safeOnly; 885 | /// 886 | @safe unittest 887 | { 888 | union Fields 889 | { 890 | int intval; 891 | @safeOnly int *ptr; 892 | } 893 | 894 | // only safe operations allowed on pointer field 895 | @safe void test() { 896 | TaggedAlgebraic!Fields x = 1; 897 | x += 5; // only applies to intval 898 | auto p = new int(5); 899 | x = p; 900 | *x += 5; // safe pointer ops allowed 901 | assert(*p == 10); 902 | } 903 | 904 | test(); 905 | } 906 | 907 | private template hasAnyMember(TA, string name) 908 | { 909 | import std.traits : isAggregateType; 910 | 911 | alias Types = TA.UnionType.FieldTypes; 912 | 913 | template impl(size_t i) { 914 | static if (i >= Types.length) enum impl = false; 915 | else { 916 | alias T = Types[i]; 917 | static if (__traits(hasMember, T, name) 918 | // work around https://issues.dlang.org/show_bug.cgi?id=20316 919 | || (is(T : Q[], Q) && (name == "length" || name == "ptr" || name == "capacity"))) 920 | enum impl = true; 921 | else enum impl = impl!(i+1); 922 | } 923 | } 924 | 925 | alias hasAnyMember = impl!0; 926 | } 927 | 928 | unittest { 929 | import std.range.primitives : isOutputRange; 930 | import std.typecons : Rebindable; 931 | 932 | struct S { int a, b; void foo() {}} 933 | interface I { void bar() immutable; } 934 | static union U { int x; S s; Rebindable!(const(I)) i; int[] a; } 935 | alias TA = TaggedAlgebraic!U; 936 | static assert(hasAnyMember!(TA, "a")); 937 | static assert(hasAnyMember!(TA, "b")); 938 | static assert(hasAnyMember!(TA, "foo")); 939 | static assert(hasAnyMember!(TA, "bar")); 940 | static assert(hasAnyMember!(TA, "length")); 941 | static assert(hasAnyMember!(TA, "ptr")); 942 | static assert(hasAnyMember!(TA, "capacity")); 943 | static assert(hasAnyMember!(TA, "sizeof")); 944 | static assert(!hasAnyMember!(TA, "put")); 945 | static assert(!isOutputRange!(TA, int)); 946 | } 947 | 948 | private template hasOp(TA, OpKind kind, string name, ARGS...) 949 | { 950 | import std.traits : CopyTypeQualifiers; 951 | alias UQ = CopyTypeQualifiers!(TA, TA.FieldDefinitionType); 952 | enum hasOp = AliasSeq!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0; 953 | } 954 | 955 | unittest { 956 | static struct S { 957 | void m(int i) {} 958 | bool opEquals(int i) { return true; } 959 | bool opEquals(S s) { return true; } 960 | } 961 | 962 | static union U { int i; string s; S st; } 963 | alias TA = TaggedAlgebraic!U; 964 | 965 | static assert(hasOp!(TA, OpKind.binary, "+", int)); 966 | static assert(hasOp!(TA, OpKind.binary, "~", string)); 967 | static assert(hasOp!(TA, OpKind.binary, "==", int)); 968 | static assert(hasOp!(TA, OpKind.binary, "==", string)); 969 | static assert(hasOp!(TA, OpKind.binary, "==", int)); 970 | static assert(hasOp!(TA, OpKind.binary, "==", S)); 971 | static assert(hasOp!(TA, OpKind.method, "m", int)); 972 | static assert(hasOp!(TA, OpKind.binary, "+=", int)); 973 | static assert(!hasOp!(TA, OpKind.binary, "~", int)); 974 | static assert(!hasOp!(TA, OpKind.binary, "~", int)); 975 | static assert(!hasOp!(TA, OpKind.method, "m", string)); 976 | static assert(!hasOp!(TA, OpKind.method, "m")); 977 | static assert(!hasOp!(const(TA), OpKind.binary, "+=", int)); 978 | static assert(!hasOp!(const(TA), OpKind.method, "m", int)); 979 | static assert(!hasOp!(TA, OpKind.method, "put", int)); 980 | 981 | static union U2 { int *i; } 982 | alias TA2 = TaggedAlgebraic!U2; 983 | 984 | static assert(hasOp!(TA2, OpKind.unary, "*")); 985 | } 986 | 987 | unittest { 988 | struct S { 989 | union U { 990 | string s; 991 | S[] arr; 992 | S[string] obj; 993 | } 994 | alias TA = TaggedAlgebraic!(S.U); 995 | TA payload; 996 | alias payload this; 997 | } 998 | static assert(hasOp!(S.TA, OpKind.index, null, size_t)); 999 | static assert(hasOp!(S.TA, OpKind.index, null, int)); 1000 | static assert(hasOp!(S.TA, OpKind.index, null, string)); 1001 | static assert(hasOp!(S.TA, OpKind.field, "length")); 1002 | } 1003 | 1004 | unittest { // "in" operator 1005 | union U { 1006 | string[string] dict; 1007 | } 1008 | alias TA = TaggedAlgebraic!U; 1009 | auto ta = TA(["foo": "bar"]); 1010 | assert("foo" in ta); 1011 | assert(*("foo" in ta) == "bar"); 1012 | } 1013 | 1014 | unittest { // issue #15 - by-ref return values 1015 | static struct S { 1016 | int x; 1017 | ref int getx() return { return x; } 1018 | } 1019 | static union U { S s; } 1020 | alias TA = TaggedAlgebraic!U; 1021 | auto ta = TA(S(10)); 1022 | assert(ta.x == 10); 1023 | ta.getx() = 11; 1024 | assert(ta.x == 11); 1025 | } 1026 | 1027 | private static auto ref implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args) 1028 | { 1029 | import std.array : join; 1030 | import std.traits : CopyTypeQualifiers; 1031 | import std.variant : Algebraic, Variant; 1032 | alias UQ = CopyTypeQualifiers!(T, T.FieldDefinitionType); 1033 | 1034 | alias info = OpInfo!(UQ, kind, name, ARGS); 1035 | 1036 | static assert(hasOp!(T, kind, name, ARGS)); 1037 | 1038 | static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type."); 1039 | 1040 | //pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof); 1041 | //pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof); 1042 | //pragma(msg, typeof(T.Union.tupleof)); 1043 | //import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes)); 1044 | 1045 | switch (self.kind) { 1046 | enum assert_msg = "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", "); 1047 | default: assert(false, assert_msg); 1048 | foreach (i, f; info.fields) { 1049 | alias FT = T.UnionType.FieldTypeByName!f; 1050 | case __traits(getMember, T.Kind, f): 1051 | static if (NoDuplicates!(info.ReturnTypes).length == 1) 1052 | return info.perform(self.m_union.trustedGet!FT, args); 1053 | else static if (allSatisfy!(isMatchingUniqueType!T, info.ReturnTypes)) 1054 | return TaggedAlgebraic!(T.FieldDefinitionType)(info.perform(self.m_union.trustedGet!FT, args)); 1055 | else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) { 1056 | alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes)); 1057 | info.ReturnTypes[i] ret = info.perform(self.m_union.trustedGet!FT, args); 1058 | import std.traits : isInstanceOf; 1059 | return Alg(ret); 1060 | } 1061 | else static if (is(FT == Variant)) 1062 | return info.perform(self.m_union.trustedGet!FT, args); 1063 | else 1064 | return Variant(info.perform(self.m_union.trustedGet!FT, args)); 1065 | } 1066 | } 1067 | 1068 | assert(false); // never reached 1069 | } 1070 | 1071 | unittest { // opIndex on recursive TA with closed return value set 1072 | static struct S { 1073 | union U { 1074 | char ch; 1075 | string str; 1076 | S[] arr; 1077 | } 1078 | alias TA = TaggedAlgebraic!U; 1079 | TA payload; 1080 | alias payload this; 1081 | 1082 | this(T)(T t) { this.payload = t; } 1083 | } 1084 | S a = S("foo"); 1085 | S s = S([a]); 1086 | 1087 | assert(implementOp!(OpKind.field, "length")(s.payload) == 1); 1088 | static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA)); 1089 | assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); 1090 | } 1091 | 1092 | unittest { // opIndex on recursive TA with closed return value set using @disableIndex 1093 | static struct S { 1094 | union U { 1095 | @disableIndex string str; 1096 | S[] arr; 1097 | } 1098 | alias TA = TaggedAlgebraic!U; 1099 | TA payload; 1100 | alias payload this; 1101 | 1102 | this(T)(T t) { this.payload = t; } 1103 | } 1104 | S a = S("foo"); 1105 | S s = S([a]); 1106 | 1107 | assert(implementOp!(OpKind.field, "length")(s.payload) == 1); 1108 | static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S)); 1109 | assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); 1110 | } 1111 | 1112 | unittest { // test safeOnly 1113 | static struct S 1114 | { 1115 | int foo() @system { return 1; } 1116 | } 1117 | 1118 | static struct T 1119 | { 1120 | string foo() @safe { return "hi"; } 1121 | } 1122 | 1123 | union GoodU { 1124 | int x; 1125 | @safeOnly int *ptr; 1126 | @safeOnly S s; 1127 | T t; 1128 | } 1129 | 1130 | union BadU { 1131 | int x; 1132 | int *ptr; 1133 | S s; 1134 | T t; 1135 | } 1136 | 1137 | union MixedU { 1138 | int x; 1139 | @safeOnly int *ptr; 1140 | S s; 1141 | T t; 1142 | } 1143 | 1144 | TaggedAlgebraic!GoodU allsafe; 1145 | TaggedAlgebraic!BadU nosafe; 1146 | TaggedAlgebraic!MixedU somesafe; 1147 | import std.variant : Algebraic; 1148 | static assert(is(typeof(allsafe += 1))); 1149 | static assert(is(typeof(allsafe.foo()) == string)); 1150 | static assert(is(typeof(nosafe += 1))); 1151 | static assert(is(typeof(nosafe.foo()) == Algebraic!(int, string))); 1152 | static assert(is(typeof(somesafe += 1))); 1153 | static assert(is(typeof(somesafe.foo()) == Algebraic!(int, string))); 1154 | 1155 | static assert( is(typeof( () @safe => allsafe += 1))); 1156 | static assert( is(typeof( () @safe => allsafe.foo()))); 1157 | static assert(!is(typeof( () @safe => nosafe += 1))); 1158 | static assert(!is(typeof( () @safe => nosafe.foo()))); 1159 | static assert( is(typeof( () @safe => somesafe += 1))); 1160 | static assert(!is(typeof( () @safe => somesafe.foo()))); 1161 | } 1162 | 1163 | 1164 | private auto ref performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) 1165 | { 1166 | static if (kind == OpKind.binary) return mixin("value "~name~" args[0]"); 1167 | else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value"); 1168 | else static if (kind == OpKind.unary) return mixin(name~" value"); 1169 | else static if (kind == OpKind.method) return __traits(getMember, value, name)(args); 1170 | else static if (kind == OpKind.field) return __traits(getMember, value, name); 1171 | else static if (kind == OpKind.index) return value[args]; 1172 | else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0]; 1173 | else static if (kind == OpKind.call) return value(args); 1174 | else static assert(false, "Unsupported kind of operator: "~kind.stringof); 1175 | } 1176 | 1177 | unittest { 1178 | union U { int i; string s; } 1179 | 1180 | { int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); } 1181 | { string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } 1182 | } 1183 | 1184 | 1185 | private auto ref performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) 1186 | { 1187 | import std.traits : isInstanceOf; 1188 | static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) { 1189 | static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) { 1190 | return performOpRaw!(U, kind, name, T, ARGS)(value, args); 1191 | } else { 1192 | alias TA = ARGS[0]; 1193 | template MTypesImpl(size_t i) { 1194 | static if (i < TA.FieldTypes.length) { 1195 | alias FT = TA.FieldTypes[i]; 1196 | static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $])))) 1197 | alias MTypesImpl = AliasSeq!(FT, MTypesImpl!(i+1)); 1198 | else alias MTypesImpl = AliasSeq!(MTypesImpl!(i+1)); 1199 | } else alias MTypesImpl = AliasSeq!(); 1200 | } 1201 | alias MTypes = NoDuplicates!(MTypesImpl!0); 1202 | static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration."); 1203 | static if (MTypes.length == 1) { 1204 | if (args[0].hasType!(MTypes[0])) 1205 | return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]); 1206 | } else { 1207 | // TODO: allow all return types (fall back to Algebraic or Variant) 1208 | foreach (FT; MTypes) { 1209 | if (args[0].hasType!FT) 1210 | return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $])); 1211 | } 1212 | } 1213 | throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch"); 1214 | } 1215 | } else return performOpRaw!(U, kind, name, T, ARGS)(value, args); 1216 | } 1217 | 1218 | unittest { 1219 | union U { int i; double d; string s; } 1220 | 1221 | { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); } 1222 | { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } 1223 | { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); } 1224 | { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); } 1225 | } 1226 | 1227 | private template canPerform(U, bool doSafe, OpKind kind, string name, T, ARGS...) 1228 | { 1229 | static if(doSafe) 1230 | @safe auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); } 1231 | else 1232 | auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); } 1233 | enum canPerform = is(typeof(&doIt!())); 1234 | } 1235 | 1236 | private template OpInfo(U, OpKind kind, string name, ARGS...) 1237 | { 1238 | import std.traits : CopyTypeQualifiers, ReturnType; 1239 | 1240 | private alias FieldKind = UnionFieldEnum!U; 1241 | private alias FieldTypes = UnionKindTypes!FieldKind; 1242 | private alias fieldNames = UnionKindNames!FieldKind; 1243 | 1244 | private template isOpEnabled(string field) 1245 | { 1246 | alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field))); 1247 | template impl(size_t i) { 1248 | static if (i < attribs.length) { 1249 | static if (is(typeof(attribs[i]) == DisableOpAttribute)) { 1250 | static if (kind == attribs[i].kind && name == attribs[i].name) 1251 | enum impl = false; 1252 | else enum impl = impl!(i+1); 1253 | } else enum impl = impl!(i+1); 1254 | } else enum impl = true; 1255 | } 1256 | enum isOpEnabled = impl!0; 1257 | } 1258 | 1259 | private template isSafeOpRequired(string field) 1260 | { 1261 | alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field))); 1262 | template impl(size_t i) { 1263 | static if (i < attribs.length) { 1264 | static if (__traits(isSame, attribs[i], safeOnly)) 1265 | enum impl = true; 1266 | else enum impl = impl!(i+1); 1267 | } else enum impl = false; 1268 | } 1269 | enum isSafeOpRequired = impl!0; 1270 | } 1271 | 1272 | template fieldsImpl(size_t i) 1273 | { 1274 | static if (i < FieldTypes.length) { 1275 | static if (isOpEnabled!(fieldNames[i]) && canPerform!(U, isSafeOpRequired!(fieldNames[i]), kind, name, FieldTypes[i], ARGS)) { 1276 | alias fieldsImpl = AliasSeq!(fieldNames[i], fieldsImpl!(i+1)); 1277 | } else alias fieldsImpl = fieldsImpl!(i+1); 1278 | } else alias fieldsImpl = AliasSeq!(); 1279 | } 1280 | alias fields = fieldsImpl!0; 1281 | 1282 | template ReturnTypesImpl(size_t i) { 1283 | static if (i < fields.length) { 1284 | alias FT = CopyTypeQualifiers!(U, TypeOf!(__traits(getMember, FieldKind, fields[i]))); 1285 | alias ReturnTypesImpl = AliasSeq!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1)); 1286 | } else alias ReturnTypesImpl = AliasSeq!(); 1287 | } 1288 | alias ReturnTypes = ReturnTypesImpl!0; 1289 | 1290 | static auto ref perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); } 1291 | } 1292 | 1293 | private template ImplicitUnqual(T) { 1294 | import std.traits : Unqual, hasAliasing; 1295 | static if (is(T == void)) alias ImplicitUnqual = void; 1296 | else { 1297 | private static struct S { T t; } 1298 | static if (hasAliasing!S) alias ImplicitUnqual = T; 1299 | else alias ImplicitUnqual = Unqual!T; 1300 | } 1301 | } 1302 | 1303 | private enum OpKind { 1304 | binary, 1305 | binaryRight, 1306 | unary, 1307 | method, 1308 | field, 1309 | index, 1310 | indexAssign, 1311 | call 1312 | } 1313 | 1314 | deprecated alias TypeEnum(U) = UnionFieldEnum!U; 1315 | 1316 | 1317 | private string generateConstructors(U)() 1318 | { 1319 | import std.algorithm : map; 1320 | import std.array : join; 1321 | import std.string : format; 1322 | import std.traits : FieldTypeTuple; 1323 | 1324 | string ret; 1325 | 1326 | 1327 | // normal type constructors 1328 | foreach (tname; UniqueTypeFields!U) 1329 | ret ~= q{ 1330 | this(UnionType.FieldTypeByName!"%1$s" value) 1331 | { 1332 | static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) 1333 | m_union.set!(Kind.%1$s)(); 1334 | else 1335 | m_union.set!(Kind.%1$s)(value); 1336 | } 1337 | 1338 | void opAssign(UnionType.FieldTypeByName!"%1$s" value) 1339 | { 1340 | static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) 1341 | m_union.set!(Kind.%1$s)(); 1342 | else 1343 | m_union.set!(Kind.%1$s)(value); 1344 | } 1345 | }.format(tname); 1346 | 1347 | // type constructors with explicit type tag 1348 | foreach (tname; AliasSeq!(UniqueTypeFields!U, AmbiguousTypeFields!U)) 1349 | ret ~= q{ 1350 | this(UnionType.FieldTypeByName!"%1$s" value, Kind type) 1351 | { 1352 | switch (type) { 1353 | foreach (i, n; TaggedUnion!U.fieldNames) { 1354 | static if (is(UnionType.FieldTypeByName!"%1$s" == UnionType.FieldTypes[i])) { 1355 | case __traits(getMember, Kind, n): 1356 | static if (isUnitType!(UnionType.FieldTypes[i])) 1357 | m_union.set!(__traits(getMember, Kind, n))(); 1358 | else m_union.set!(__traits(getMember, Kind, n))(value); 1359 | return; 1360 | } 1361 | } 1362 | // NOTE: the default case needs to be at the bottom to avoid bogus 1363 | // unreachable code warnings (DMD issue 21671) 1364 | default: assert(false, format("Invalid type ID for type %%s: %%s", UnionType.FieldTypeByName!"%1$s".stringof, type)); 1365 | } 1366 | } 1367 | }.format(tname); 1368 | 1369 | return ret; 1370 | } 1371 | 1372 | private template UniqueTypeFields(U) { 1373 | alias Enum = UnionFieldEnum!U; 1374 | alias Types = UnionKindTypes!Enum; 1375 | alias indices = UniqueTypes!Types; 1376 | enum toName(int i) = UnionKindNames!Enum[i]; 1377 | alias UniqueTypeFields = staticMap!(toName, indices); 1378 | } 1379 | 1380 | private template AmbiguousTypeFields(U) { 1381 | alias Enum = UnionFieldEnum!U; 1382 | alias Types = UnionKindTypes!Enum; 1383 | alias indices = AmbiguousTypes!Types; 1384 | enum toName(int i) = UnionKindNames!Enum[i]; 1385 | alias AmbiguousTypeFields = staticMap!(toName, indices); 1386 | } 1387 | 1388 | unittest { 1389 | union U { 1390 | int a; 1391 | string b; 1392 | int c; 1393 | double d; 1394 | } 1395 | static assert([UniqueTypeFields!U] == ["b", "d"]); 1396 | static assert([AmbiguousTypeFields!U] == ["a"]); 1397 | } 1398 | 1399 | private template isMatchingUniqueType(TA) { 1400 | import std.traits : staticMap; 1401 | alias FieldTypes = UnionKindTypes!(UnionFieldEnum!(TA.FieldDefinitionType)); 1402 | alias F(size_t i) = FieldTypes[i]; 1403 | alias UniqueTypes = staticMap!(F, .UniqueTypes!FieldTypes); 1404 | template isMatchingUniqueType(T) { 1405 | static if (is(T : TA)) enum isMatchingUniqueType = true; 1406 | else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0; 1407 | } 1408 | } 1409 | 1410 | unittest { 1411 | union U { 1412 | int i; 1413 | TaggedAlgebraic!This[] array; 1414 | } 1415 | alias TA = TaggedAlgebraic!U; 1416 | alias pass(alias templ, T) = templ!T; 1417 | static assert(pass!(isMatchingUniqueType!TA, TaggedAlgebraic!U)); 1418 | static assert(!pass!(isMatchingUniqueType!TA, string)); 1419 | static assert(pass!(isMatchingUniqueType!TA, int)); 1420 | static assert(pass!(isMatchingUniqueType!TA, (TaggedAlgebraic!U[]))); 1421 | } 1422 | 1423 | private template fieldMatchesType(U, T) 1424 | { 1425 | enum fieldMatchesType(string field) = is(TypeOf!(__traits(getMember, UnionFieldEnum!U, field)) == T); 1426 | } 1427 | 1428 | private template FieldTypeOf(U) { 1429 | template FieldTypeOf(string name) { 1430 | alias FieldTypeOf = TypeOf!(__traits(getMember, UnionFieldEnum!U, name)); 1431 | } 1432 | } 1433 | 1434 | private template staticIndexOfImplicit(T, Types...) { 1435 | template impl(size_t i) { 1436 | static if (i < Types.length) { 1437 | static if (is(T : Types[i])) enum impl = i; 1438 | else enum impl = impl!(i+1); 1439 | } else enum impl = -1; 1440 | } 1441 | enum staticIndexOfImplicit = impl!0; 1442 | } 1443 | 1444 | unittest { 1445 | static assert(staticIndexOfImplicit!(immutable(char), char) == 0); 1446 | static assert(staticIndexOfImplicit!(int, long) == 0); 1447 | static assert(staticIndexOfImplicit!(long, int) < 0); 1448 | static assert(staticIndexOfImplicit!(int, int, double) == 0); 1449 | static assert(staticIndexOfImplicit!(double, int, double) == 1); 1450 | } 1451 | 1452 | 1453 | private template isNoVariant(T) { 1454 | import std.variant : Variant; 1455 | enum isNoVariant = !is(T == Variant); 1456 | } 1457 | 1458 | 1459 | unittest { 1460 | struct TU { int i; } 1461 | alias TA = TaggedAlgebraic!TU; 1462 | 1463 | auto ta = TA(12); 1464 | static assert(!is(typeof(ta.put(12)))); 1465 | } 1466 | 1467 | @safe nothrow unittest { 1468 | struct TU { int i; string s; } 1469 | alias TA = TaggedAlgebraic!TU; 1470 | 1471 | static assert(is(typeof(TA.init.toHash()) == size_t)); 1472 | 1473 | int[TA] aa; 1474 | aa[TA(1)] = 1; 1475 | aa[TA("foo")] = 2; 1476 | 1477 | assert(aa[TA(1)] == 1); 1478 | assert(aa[TA("foo")] == 2); 1479 | } 1480 | -------------------------------------------------------------------------------- /source/taggedalgebraic/taggedunion.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic tagged union and algebraic data type implementations. 3 | * 4 | * Copyright: Copyright 2015-2019, Sönke Ludwig. 5 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 | * Authors: Sönke Ludwig 7 | */ 8 | module taggedalgebraic.taggedunion; 9 | 10 | // scheduled for deprecation 11 | static import taggedalgebraic.visit; 12 | alias visit = taggedalgebraic.visit.visit; 13 | 14 | import std.algorithm.mutation : move, swap; 15 | import std.meta; 16 | import std.range : isOutputRange; 17 | import std.traits : EnumMembers, FieldNameTuple, Unqual, hasUDA, isInstanceOf; 18 | 19 | 20 | /** Implements a generic tagged union type. 21 | 22 | This struct takes a `union` or `struct` declaration as an input and builds 23 | an algebraic data type from its fields, using an automatically generated 24 | `Kind` enumeration to identify which field of the union is currently used. 25 | Multiple fields with the same value are supported. 26 | 27 | For each field defined by `U` a number of convenience members are generated. 28 | For a given field "foo", these fields are: 29 | 30 | $(UL 31 | $(LI `static foo(value)` - returns a new tagged union with the specified value) 32 | $(LI `isFoo` - equivalent to `kind == Kind.foo`) 33 | $(LI `setFoo(value)` - equivalent to `set!(Kind.foo)(value)`) 34 | $(LI `getFoo` - equivalent to `get!(Kind.foo)`) 35 | ) 36 | */ 37 | template TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) { 38 | align(commonAlignment!(UnionKindTypes!(UnionFieldEnum!U))) struct TaggedUnion 39 | { 40 | import std.traits : FieldTypeTuple, FieldNameTuple, Largest, 41 | hasElaborateCopyConstructor, hasElaborateDestructor, isCopyable; 42 | import std.meta : templateOr; 43 | import std.ascii : toUpper; 44 | 45 | alias FieldDefinitionType = U; 46 | 47 | /// A type enum that identifies the type of value currently stored. 48 | alias Kind = UnionFieldEnum!U; 49 | 50 | alias FieldTypes = UnionKindTypes!Kind; 51 | alias fieldNames = UnionKindNames!Kind; 52 | 53 | static assert(FieldTypes.length > 0, "The TaggedUnions's union type must have at least one field."); 54 | static assert(FieldTypes.length == fieldNames.length); 55 | 56 | package alias FieldTypeByName(string name) = FieldTypes[__traits(getMember, Kind, name)]; 57 | 58 | private { 59 | static if (isUnitType!(FieldTypes[0]) || __VERSION__ < 2072) { 60 | void[Largest!FieldTypes.sizeof] m_data; 61 | @property ref inout(void[Largest!FieldTypes.sizeof]) rawData() inout @safe return { return m_data; } 62 | } else { 63 | union Dummy { 64 | FieldTypes[0] initField; 65 | void[Largest!FieldTypes.sizeof] data; 66 | } 67 | Dummy m_data = { initField: FieldTypes[0].init }; 68 | @property ref inout(void[Largest!FieldTypes.sizeof]) rawData() inout @trusted return { return m_data.data; } 69 | } 70 | Kind m_kind; 71 | } 72 | 73 | this(TaggedUnion other) 74 | { 75 | rawSwap(this, other); 76 | } 77 | 78 | void opAssign(TaggedUnion other) 79 | { 80 | rawSwap(this, other); 81 | } 82 | 83 | static foreach (ti; UniqueTypes!FieldTypes) 84 | static if(!is(FieldTypes[ti] == void)) 85 | { 86 | this(FieldTypes[ti] value) 87 | { 88 | static if (isUnitType!(FieldTypes[ti])) 89 | set!(cast(Kind)ti)(); 90 | else 91 | set!(cast(Kind)ti)(.move(value)); 92 | } 93 | 94 | void opAssign(FieldTypes[ti] value) 95 | { 96 | static if (isUnitType!(FieldTypes[ti])) 97 | set!(cast(Kind)ti)(); 98 | else 99 | set!(cast(Kind)ti)(.move(value)); 100 | } 101 | } 102 | 103 | // disable default construction if first type is not a null/Void type 104 | static if (!isUnitType!(FieldTypes[0]) && __VERSION__ < 2072) { 105 | @disable this(); 106 | } 107 | 108 | // postblit constructor 109 | static if (!allSatisfy!(templateOr!(isCopyable, isUnitType), FieldTypes)) { 110 | @disable this(this); 111 | } else static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes)) { 112 | this(this) 113 | { 114 | switch (m_kind) { 115 | default: break; 116 | foreach (i, tname; fieldNames) { 117 | alias T = FieldTypes[i]; 118 | static if (hasElaborateCopyConstructor!T) 119 | { 120 | case __traits(getMember, Kind, tname): 121 | static if (hasUDA!(U, typeof(forceNothrowPostblit()))) { 122 | try typeid(T).postblit(cast(void*)&trustedGet!T()); 123 | catch (Exception e) assert(false, e.msg); 124 | } else { 125 | typeid(T).postblit(cast(void*)&trustedGet!T()); 126 | } 127 | return; 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | // destructor 135 | static if (anySatisfy!(hasElaborateDestructor, FieldTypes)) { 136 | ~this() 137 | { 138 | final switch (m_kind) { 139 | foreach (i, tname; fieldNames) { 140 | alias T = FieldTypes[i]; 141 | case __traits(getMember, Kind, tname): 142 | static if (hasElaborateDestructor!T) { 143 | .destroy(trustedGet!T); 144 | } 145 | return; 146 | } 147 | } 148 | } 149 | } 150 | 151 | /// Enables conversion or extraction of the stored value. 152 | T opCast(T)() 153 | { 154 | import std.conv : to; 155 | 156 | final switch (m_kind) { 157 | foreach (i, FT; FieldTypes) { 158 | case __traits(getMember, Kind, fieldNames[i]): 159 | static if (is(typeof(trustedGet!FT) : T)) 160 | return trustedGet!FT; 161 | else static if (is(typeof(to!T(trustedGet!FT)))) { 162 | return to!T(trustedGet!FT); 163 | } else { 164 | assert(false, "Cannot cast a " ~ fieldNames[i] 165 | ~ " value of type " ~ FT.stringof ~ " to " ~ T.stringof); 166 | } 167 | } 168 | } 169 | assert(false); // never reached 170 | } 171 | /// ditto 172 | T opCast(T)() const 173 | { 174 | // this method needs to be duplicated because inout doesn't work with to!() 175 | import std.conv : to; 176 | 177 | final switch (m_kind) { 178 | foreach (i, FT; FieldTypes) { 179 | case __traits(getMember, Kind, fieldNames[i]): 180 | static if (is(typeof(trustedGet!FT) : T)) 181 | return trustedGet!FT; 182 | else static if (is(typeof(to!T(trustedGet!FT)))) { 183 | return to!T(trustedGet!FT); 184 | } else { 185 | assert(false, "Cannot cast a " ~ fieldNames[i] 186 | ~ " value of type" ~ FT.stringof ~ " to " ~ T.stringof); 187 | } 188 | } 189 | } 190 | assert(false); // never reached 191 | } 192 | 193 | /// Enables equality comparison with the stored value. 194 | bool opEquals()(auto ref inout(TaggedUnion) other) 195 | inout { 196 | if (this.kind != other.kind) return false; 197 | 198 | final switch (this.kind) { 199 | foreach (i, fname; TaggedUnion!U.fieldNames) 200 | case __traits(getMember, Kind, fname): 201 | return trustedGet!(FieldTypes[i]) == other.trustedGet!(FieldTypes[i]); 202 | } 203 | assert(false); // never reached 204 | } 205 | 206 | static if (allSatisfy!(isHashable, FieldTypes)) 207 | { 208 | /// Enables using a tagged union value as an associative array key. 209 | size_t toHash() 210 | const @safe nothrow { 211 | size_t ret; 212 | final switch (m_kind) { 213 | foreach (i, tname; fieldNames) { 214 | alias T = FieldTypes[i]; 215 | case __traits(getMember, Kind, tname): 216 | static if (!isUnitType!T) { 217 | ret = hashOf(trustedGet!T); 218 | } 219 | break; 220 | } 221 | } 222 | return ret ^ (m_kind * 0xBA7A57E3); 223 | } 224 | } 225 | 226 | /// The type ID of the currently stored value. 227 | @property Kind kind() const { return m_kind; } 228 | 229 | static foreach (i, name; fieldNames) { 230 | // NOTE: using getX/setX here because using just x would be prone to 231 | // misuse (attempting to "get" a value for modification when 232 | // a different kind is set instead of assigning a new value) 233 | mixin("alias set"~pascalCase(name)~" = set!(Kind."~name~");"); 234 | mixin("@property bool is"~pascalCase(name)~"() const { return m_kind == Kind."~name~"; }"); 235 | 236 | static if (name[$-1] == '_') { 237 | mixin("alias set"~pascalCase(name[0 .. $-1])~" = set!(Kind."~name~");"); 238 | mixin("@property bool is"~pascalCase(name[0 .. $-1])~"() const { return m_kind == Kind."~name~"; }"); 239 | } 240 | 241 | static if (!isUnitType!(FieldTypes[i])) { 242 | mixin("alias "~name~"Value = value!(Kind."~name~");"); 243 | 244 | // remove trailing underscore from names like "int_" 245 | static if (name[$-1] == '_') 246 | mixin("alias "~name[0 .. $-1]~"Value = value!(Kind."~name~");"); 247 | 248 | mixin("static TaggedUnion "~name~"(FieldTypes["~i.stringof~"] value)" 249 | ~ "{ TaggedUnion tu; tu.set!(Kind."~name~")(.move(value)); return tu; }"); 250 | 251 | // TODO: define assignment operator for unique types 252 | } else { 253 | mixin("static @property TaggedUnion "~name~"() { TaggedUnion tu; tu.set!(Kind."~name~"); return tu; }"); 254 | } 255 | } 256 | 257 | /** Checks whether the currently stored value has a given type. 258 | */ 259 | @property bool hasType(T)() 260 | const { 261 | static assert(staticIndexOf!(T, FieldTypes) >= 0, "Type "~T.stringof~ " not part of "~FieldTypes.stringof); 262 | 263 | final switch (this.kind) { 264 | static foreach (i, n; fieldNames) { 265 | case __traits(getMember, Kind, n): 266 | return is(FieldTypes[i] == T); 267 | } 268 | } 269 | } 270 | 271 | /** Accesses the contained value by reference. 272 | 273 | The specified `kind` must equal the current value of the `this.kind` 274 | property. Setting a different type must be done with `set` or `opAssign` 275 | instead. 276 | 277 | See_Also: `set`, `opAssign` 278 | */ 279 | @property ref value(Kind kind)() 280 | inout { 281 | if (this.kind != kind) { 282 | enum msg(.string k_is) = "Attempt to get kind "~kind.stringof~" from tagged union with kind "~k_is; 283 | final switch (this.kind) { 284 | static foreach (i, n; fieldNames) 285 | case __traits(getMember, Kind, n): 286 | assert(false, msg!n); 287 | } 288 | } 289 | return trustedGet!(FieldTypes[kind]); 290 | } 291 | 292 | 293 | /** Accesses the contained value by reference. 294 | 295 | The specified type `T` must equal the type of the currently set value. 296 | Setting a different type must be done with `set` or `opAssign` instead. 297 | 298 | See_Also: `set`, `opAssign` 299 | */ 300 | @property ref inout(T) value(T)() inout 301 | { 302 | static assert(staticIndexOf!(T, FieldTypes) >= 0, "Type "~T.stringof~ " not part of "~FieldTypes.stringof); 303 | 304 | final switch (this.kind) { 305 | static foreach (i, n; fieldNames) { 306 | case __traits(getMember, Kind, n): 307 | static if (is(FieldTypes[i] == T)) 308 | return trustedGet!T; 309 | else assert(false, "Attempting to get type "~T.stringof 310 | ~ " from a TaggedUnion with type " 311 | ~ FieldTypes[__traits(getMember, Kind, n)].stringof); 312 | } 313 | } 314 | } 315 | 316 | /** Sets a new value of the specified `kind`. 317 | */ 318 | ref FieldTypes[kind] set(Kind kind)(FieldTypes[kind] value) 319 | if (!isUnitType!(FieldTypes[kind])) 320 | { 321 | if (m_kind != kind) { 322 | destroy(this); 323 | this.rawData.rawEmplace(value); 324 | } else { 325 | rawSwap(trustedGet!(FieldTypes[kind]), value); 326 | } 327 | m_kind = kind; 328 | 329 | return trustedGet!(FieldTypes[kind]); 330 | } 331 | 332 | /** Sets a `void` value of the specified kind. 333 | */ 334 | void set(Kind kind)() 335 | if (isUnitType!(FieldTypes[kind])) 336 | { 337 | if (m_kind != kind) { 338 | destroy(this); 339 | } 340 | m_kind = kind; 341 | } 342 | 343 | /** Converts the contained value to a string. 344 | 345 | The format output by this method is "(kind: value)", where "kind" is 346 | the enum name of the currently stored type and "value" is the string 347 | representation of the stored value. 348 | */ 349 | void toString(W)(ref W w) const 350 | if (isOutputRange!(W, char)) 351 | { 352 | import std.range.primitives : put; 353 | import std.conv : text; 354 | import std.format : FormatSpec, formatValue; 355 | 356 | final switch (m_kind) { 357 | foreach (i, v; EnumMembers!Kind) { 358 | case v: 359 | enum vstr = text(v); 360 | static if (isUnitType!(FieldTypes[i])) put(w, vstr); 361 | else { 362 | // NOTE: using formatValue instead of formattedWrite 363 | // because formattedWrite does not work for 364 | // non-copyable types 365 | enum prefix = "(" ~ vstr ~ ": "; 366 | enum suffix = ")"; 367 | put(w, prefix); 368 | FormatSpec!char spec; 369 | formatValue(w, value!v, spec); 370 | put(w, suffix); 371 | } 372 | break; 373 | } 374 | } 375 | } 376 | 377 | package @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)this.rawData.ptr; } 378 | } 379 | } 380 | 381 | /// 382 | @safe nothrow unittest { 383 | union Kinds { 384 | int count; 385 | string text; 386 | } 387 | alias TU = TaggedUnion!Kinds; 388 | 389 | // default initialized to the first field defined 390 | TU tu; 391 | assert(tu.kind == TU.Kind.count); 392 | assert(tu.isCount); // equivalent to the line above 393 | assert(!tu.isText); 394 | assert(tu.value!(TU.Kind.count) == int.init); 395 | 396 | // set to a specific count 397 | tu.setCount(42); 398 | assert(tu.isCount); 399 | assert(tu.countValue == 42); 400 | assert(tu.value!(TU.Kind.count) == 42); 401 | assert(tu.value!int == 42); // can also get by type 402 | assert(tu.countValue == 42); 403 | 404 | // assign a new tagged algebraic value 405 | tu = TU.count(43); 406 | 407 | // test equivalence with other tagged unions 408 | assert(tu == TU.count(43)); 409 | assert(tu != TU.count(42)); 410 | assert(tu != TU.text("hello")); 411 | 412 | // modify by reference 413 | tu.countValue++; 414 | assert(tu.countValue == 44); 415 | 416 | // set the second field 417 | tu.setText("hello"); 418 | assert(!tu.isCount); 419 | assert(tu.isText); 420 | assert(tu.kind == TU.Kind.text); 421 | assert(tu.textValue == "hello"); 422 | 423 | // unique types can also be directly constructed 424 | tu = TU(12); 425 | assert(tu.countValue == 12); 426 | tu = TU("foo"); 427 | assert(tu.textValue == "foo"); 428 | } 429 | 430 | /// 431 | @safe nothrow unittest { 432 | // Enum annotations supported since DMD 2.082.0. The mixin below is 433 | // necessary to keep the parser happy on older versions. 434 | static if (__VERSION__ >= 2082) { 435 | alias myint = int; 436 | // tagged unions can be defined in terms of an annotated enum 437 | mixin(q{enum E { 438 | none, 439 | @string text 440 | }}); 441 | 442 | alias TU = TaggedUnion!E; 443 | static assert(is(TU.Kind == E)); 444 | 445 | TU tu; 446 | assert(tu.isNone); 447 | assert(tu.kind == E.none); 448 | 449 | tu.setText("foo"); 450 | assert(tu.kind == E.text); 451 | assert(tu.textValue == "foo"); 452 | } 453 | } 454 | 455 | unittest { // test for name clashes 456 | union U { .string string; } 457 | alias TU = TaggedUnion!U; 458 | TU tu; 459 | tu = TU.string("foo"); 460 | assert(tu.isString); 461 | assert(tu.stringValue == "foo"); 462 | } 463 | 464 | unittest { // test woraround for Phobos issue 19696 465 | struct T { 466 | struct F { int num; } 467 | alias Payload = TaggedUnion!F; 468 | Payload payload; 469 | alias payload this; 470 | } 471 | 472 | struct U { 473 | T t; 474 | } 475 | 476 | alias TU = TaggedUnion!U; 477 | static assert(is(TU.FieldTypes[0] == T)); 478 | } 479 | 480 | unittest { // non-copyable types 481 | import std.traits : isCopyable; 482 | 483 | struct S { @disable this(this); } 484 | struct U { 485 | int i; 486 | S s; 487 | } 488 | alias TU = TaggedUnion!U; 489 | static assert(!isCopyable!TU); 490 | 491 | auto tu = TU(42); 492 | tu.setS(S.init); 493 | } 494 | 495 | unittest { // alignment 496 | union S1 { int v; } 497 | union S2 { ulong v; } 498 | union S3 { void* v; } 499 | 500 | // sanity check for the actual checks - this may differ on non-x86 architectures 501 | static assert(S1.alignof == 4); 502 | static assert(S2.alignof == 8); 503 | version (D_LP64) static assert(S3.alignof == 8); 504 | else static assert(S3.alignof == 4); 505 | 506 | // test external struct alignment 507 | static assert(TaggedUnion!S1.alignof == 4); 508 | static assert(TaggedUnion!S2.alignof == 8); 509 | version (D_LP64) static assert(TaggedUnion!S3.alignof == 8); 510 | else static assert(TaggedUnion!S3.alignof == 4); 511 | 512 | // test internal struct alignment 513 | TaggedUnion!S1 s1; 514 | assert((cast(ubyte*)&s1.vValue() - cast(ubyte*)&s1) % 4 == 0); 515 | TaggedUnion!S1 s2; 516 | assert((cast(ubyte*)&s2.vValue() - cast(ubyte*)&s2) % 8 == 0); 517 | TaggedUnion!S1 s3; 518 | version (D_LP64) assert((cast(ubyte*)&s3.vValue() - cast(ubyte*)&s3) % 8 == 0); 519 | else assert((cast(ubyte*)&s3.vValue() - cast(ubyte*)&s3) % 4 == 0); 520 | } 521 | 522 | unittest { // toString 523 | import std.conv : to; 524 | 525 | static struct NoCopy { 526 | @disable this(this); 527 | string toString() const { return "foo"; } 528 | } 529 | 530 | union U { Void empty; int i; NoCopy noCopy; } 531 | TaggedUnion!U val; 532 | assert(val.to!string == "empty"); 533 | val.setI(42); 534 | assert(val.to!string == "(i: 42)"); 535 | val.setNoCopy(NoCopy.init); 536 | assert(val.to!string == "(noCopy: foo)"); 537 | } 538 | 539 | unittest { // null members should be assignable 540 | union U { int i; typeof(null) Null; } 541 | TaggedUnion!U val; 542 | val = null; 543 | assert(val.kind == val.Kind.Null); 544 | val = TaggedUnion!U(null); 545 | assert(val.kind == val.Kind.Null); 546 | } 547 | 548 | unittest { // make sure field names don't conflict with function names 549 | union U { int to; int move; int swap; } 550 | TaggedUnion!U val; 551 | val.setMove(1); 552 | } 553 | 554 | unittest { // support trailing underscores properly 555 | union U { 556 | int int_; 557 | } 558 | TaggedUnion!U val; 559 | 560 | val = TaggedUnion!U.int_(10); 561 | assert(val.int_Value == 10); 562 | assert(val.intValue == 10); 563 | assert(val.isInt_); 564 | assert(val.isInt); 565 | val.setInt_(20); 566 | val.setInt(20); 567 | assert(val.intValue == 20); 568 | } 569 | 570 | @safe nothrow unittest { 571 | static struct S { int i; string s; } 572 | alias TU = TaggedUnion!S; 573 | 574 | static assert(is(typeof(TU.init.toHash()) == size_t)); 575 | 576 | int[TU] aa; 577 | aa[TU(1)] = 1; 578 | aa[TU("foo")] = 2; 579 | 580 | assert(aa[TU(1)] == 1); 581 | assert(aa[TU("foo")] == 2); 582 | } 583 | 584 | 585 | @property auto forceNothrowPostblit() 586 | { 587 | if (!__ctfe) assert(false, "@forceNothrowPostblit must only be used as a UDA."); 588 | static struct R {} 589 | return R.init; 590 | } 591 | 592 | nothrow unittest { 593 | static struct S { 594 | this(this) nothrow {} 595 | } 596 | 597 | @forceNothrowPostblit 598 | struct U { 599 | S s; 600 | } 601 | 602 | alias TU = TaggedUnion!U; 603 | 604 | TU tu, tv; 605 | tu = S.init; 606 | tv = tu; 607 | } 608 | 609 | unittest { 610 | static struct S1 { int num; } 611 | alias T1 = TaggedUnion!S1; 612 | static assert(is(const(T1) : T1)); 613 | static assert(is(typeof(T1.init.value!int) == int)); 614 | static assert(is(typeof(T1.init.value!(T1.Kind.num)) == int)); 615 | static assert(is(typeof(T1.init.numValue) == int)); 616 | static assert(is(typeof(const(T1).init.value!int) == const(int))); 617 | static assert(is(typeof(const(T1).init.value!(T1.Kind.num)) == const(int))); 618 | static assert(is(typeof(const(T1).init.numValue) == const(int))); 619 | 620 | static struct S2 { int[] nums; } 621 | alias T2 = TaggedUnion!S2; 622 | static assert(!is(const(T2) : T2)); 623 | } 624 | 625 | 626 | enum isUnitType(T) = is(T == Void) || is(T == void) || is(T == typeof(null)); 627 | 628 | 629 | private string pascalCase(string camel_case) 630 | { 631 | if (!__ctfe) assert(false); 632 | import std.ascii : toUpper; 633 | return camel_case[0].toUpper ~ camel_case[1 .. $]; 634 | } 635 | 636 | /** Maps a kind enumeration value to the corresponding field type. 637 | 638 | `kind` must be a value of the `TaggedAlgebraic!T.Kind` enumeration. 639 | */ 640 | template TypeOf(alias kind) 641 | if (is(typeof(kind) == enum)) 642 | { 643 | import std.traits : FieldTypeTuple, TemplateArgsOf; 644 | import std.typecons : ReplaceType; 645 | 646 | static if (isInstanceOf!(UnionFieldEnum, typeof(kind))) { 647 | alias U = TemplateArgsOf!(typeof(kind)); 648 | alias FT = FieldTypeTuple!U[kind]; 649 | } else { 650 | alias U = typeof(kind); 651 | alias Types = UnionKindTypes!(typeof(kind)); 652 | alias uda = AliasSeq!(__traits(getAttributes, kind)); 653 | static if (uda.length == 0) alias FT = void; 654 | else alias FT = uda[0]; 655 | } 656 | 657 | // NOTE: ReplaceType has issues with certain types, such as a class 658 | // declaration like this: class C : D!C {} 659 | // For this reason, we test first if it compiles and only then use it. 660 | // It also replaces a type with the contained "alias this" type under 661 | // certain conditions, so we make a second check to see heuristically 662 | // if This is actually present in FT 663 | // 664 | // Phobos issues: 19696, 19697 665 | static if (is(ReplaceType!(This, U, FT)) && !is(ReplaceType!(This, void, FT))) 666 | alias TypeOf = ReplaceType!(This, U, FT); 667 | else alias TypeOf = FT; 668 | } 669 | 670 | /// 671 | unittest { 672 | static struct S { 673 | int a; 674 | string b; 675 | string c; 676 | } 677 | alias TU = TaggedUnion!S; 678 | 679 | static assert(is(TypeOf!(TU.Kind.a) == int)); 680 | static assert(is(TypeOf!(TU.Kind.b) == string)); 681 | static assert(is(TypeOf!(TU.Kind.c) == string)); 682 | } 683 | 684 | unittest { 685 | struct S { 686 | TaggedUnion!This[] test; 687 | } 688 | alias TU = TaggedUnion!S; 689 | 690 | TypeOf!(TU.Kind.test) a; 691 | 692 | static assert(is(TypeOf!(TU.Kind.test) == TaggedUnion!S[])); 693 | } 694 | 695 | 696 | /// Convenience type that can be used for union fields that have no value (`void` is not allowed). 697 | struct Void {} 698 | 699 | /** Special type used as a placeholder for `U` within the definition of `U` to 700 | enable self-referential types. 701 | 702 | Note that this is recognized only if used as the first argument to a 703 | template type. 704 | */ 705 | struct This { Void nothing; } 706 | 707 | /// 708 | unittest { 709 | union U { 710 | TaggedUnion!This[] list; 711 | int number; 712 | string text; 713 | } 714 | alias Node = TaggedUnion!U; 715 | 716 | auto n = Node([Node(12), Node("foo")]); 717 | assert(n.isList); 718 | assert(n.listValue == [Node(12), Node("foo")]); 719 | } 720 | 721 | package template UnionFieldEnum(U) 722 | { 723 | static if (is(U == enum)) alias UnionFieldEnum = U; 724 | else { 725 | import std.array : join; 726 | import std.traits : FieldNameTuple; 727 | mixin("enum UnionFieldEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }"); 728 | } 729 | } 730 | 731 | deprecated alias TypeEnum(U) = UnionFieldEnum!U; 732 | 733 | package alias UnionKindTypes(FieldEnum) = staticMap!(TypeOf, EnumMembers!FieldEnum); 734 | package alias UnionKindNames(FieldEnum) = AliasSeq!(__traits(allMembers, FieldEnum)); 735 | 736 | package template UniqueTypes(Types...) { 737 | template impl(size_t i) { 738 | static if (i < Types.length) { 739 | alias T = Types[i]; 740 | static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0) 741 | alias impl = AliasSeq!(i, impl!(i+1)); 742 | else alias impl = AliasSeq!(impl!(i+1)); 743 | } else alias impl = AliasSeq!(); 744 | } 745 | alias UniqueTypes = impl!0; 746 | } 747 | 748 | package template AmbiguousTypes(Types...) { 749 | template impl(size_t i) { 750 | static if (i < Types.length) { 751 | alias T = Types[i]; 752 | static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0) 753 | alias impl = AliasSeq!(i, impl!(i+1)); 754 | else alias impl = impl!(i+1); 755 | } else alias impl = AliasSeq!(); 756 | } 757 | alias AmbiguousTypes = impl!0; 758 | } 759 | 760 | /// Computes the minimum alignment necessary to align all types correctly 761 | private size_t commonAlignment(TYPES...)() 762 | { 763 | import std.numeric : gcd; 764 | 765 | size_t ret = 1; 766 | foreach (T; TYPES) 767 | ret = (T.alignof * ret) / gcd(T.alignof, ret); 768 | return ret; 769 | } 770 | 771 | unittest { 772 | align(2) struct S1 { ubyte x; } 773 | align(4) struct S2 { ubyte x; } 774 | align(8) struct S3 { ubyte x; } 775 | 776 | static if (__VERSION__ > 2076) { // DMD 2.076 ignores the alignment 777 | assert(commonAlignment!S1 == 2); 778 | assert(commonAlignment!S2 == 4); 779 | assert(commonAlignment!S3 == 8); 780 | assert(commonAlignment!(S1, S3) == 8); 781 | assert(commonAlignment!(S1, S2, S3) == 8); 782 | assert(commonAlignment!(S2, S2, S1) == 4); 783 | } 784 | } 785 | 786 | private template isHashable(T) 787 | { 788 | static if (isUnitType!T) enum isHashable = true; 789 | else static if (__traits(compiles, (ref const(T) val) @safe nothrow => hashOf(val))) 790 | enum isHashable = true; 791 | else enum isHashable = false; 792 | } 793 | 794 | package void rawEmplace(T)(void[] dst, ref T src) 795 | { 796 | T[] tdst = () @trusted { return cast(T[])dst[0 .. T.sizeof]; } (); 797 | static if (is(T == class)) { 798 | tdst[0] = src; 799 | } else { 800 | import std.conv : emplace; 801 | emplace!T(&tdst[0]); 802 | rawSwap(tdst[0], src); 803 | } 804 | } 805 | 806 | // std.algorithm.mutation.swap sometimes fails to compile due to 807 | // internal errors in hasElaborateAssign!T/isAssignable!T. This is probably 808 | // caused by cyclic dependencies. However, there is no reason to do these 809 | // checks in this context, so we just directly move the raw memory. 810 | package void rawSwap(T)(ref T a, ref T b) 811 | @trusted { 812 | void[T.sizeof] tmp = void; 813 | void[] ab = (cast(void*)&a)[0 .. T.sizeof]; 814 | void[] bb = (cast(void*)&b)[0 .. T.sizeof]; 815 | tmp[] = ab[]; 816 | ab[] = bb[]; 817 | bb[] = tmp[]; 818 | } 819 | -------------------------------------------------------------------------------- /source/taggedalgebraic/visit.d: -------------------------------------------------------------------------------- 1 | module taggedalgebraic.visit; 2 | 3 | import taggedalgebraic.taggedalgebraic; 4 | import taggedalgebraic.taggedunion; 5 | 6 | import std.meta : anySatisfy, staticMap; 7 | import std.traits : Unqual, EnumMembers, isInstanceOf; 8 | 9 | 10 | /** Dispatches the value contained on a `TaggedUnion` or `TaggedAlgebraic` to a 11 | set of visitors. 12 | 13 | A visitor can have one of three forms: 14 | 15 | $(UL 16 | $(LI function or delegate taking a single typed parameter) 17 | $(LI function or delegate taking no parameters) 18 | $(LI function or delegate template taking any single parameter) 19 | ) 20 | 21 | .... 22 | */ 23 | template visit(VISITORS...) 24 | if (VISITORS.length > 0) 25 | { 26 | auto visit(TU)(auto ref TU tu) 27 | if (isInstanceOf!(TaggedUnion, TU)) 28 | { 29 | alias val = validateHandlers!(TU, VISITORS); 30 | 31 | final switch (tu.kind) { 32 | static foreach (k; EnumMembers!(TU.Kind)) { 33 | case k: { 34 | static if (isUnitType!(TU.FieldTypes[k])) 35 | alias T = void; 36 | else alias T = TU.FieldTypes[k]; 37 | alias h = selectHandler!(T, VISITORS); 38 | static if (is(typeof(h) == typeof(null))) static assert(false, "No visitor defined for type "~T.stringof); 39 | else static if (is(typeof(h) == string)) static assert(false, h); 40 | else static if (is(T == void)) return h(); 41 | else return h(tu.value!k); 42 | } 43 | } 44 | } 45 | } 46 | 47 | auto visit(U)(auto ref TaggedAlgebraic!U ta) 48 | { 49 | return visit(ta.get!(TaggedUnion!U)); 50 | } 51 | } 52 | 53 | /// 54 | unittest { 55 | static if (__VERSION__ >= 2081) { 56 | import std.conv : to; 57 | 58 | union U { 59 | int number; 60 | string text; 61 | } 62 | alias TU = TaggedUnion!U; 63 | 64 | auto tu = TU.number(42); 65 | tu.visit!( 66 | (int n) { assert(n == 42); }, 67 | (string s) { assert(false); } 68 | ); 69 | 70 | assert(tu.visit!((v) => to!int(v)) == 42); 71 | 72 | tu.setText("43"); 73 | 74 | assert(tu.visit!((v) => to!int(v)) == 43); 75 | } 76 | } 77 | 78 | unittest { 79 | // repeat test from TaggedUnion 80 | union U { 81 | Void none; 82 | int count; 83 | float length; 84 | } 85 | TaggedAlgebraic!U u; 86 | 87 | // 88 | static assert(is(typeof(u.visit!((int) {}, (float) {}, () {})))); 89 | static assert(is(typeof(u.visit!((_) {}, () {})))); 90 | static assert(is(typeof(u.visit!((_) {}, (float) {}, () {})))); 91 | static assert(is(typeof(u.visit!((float) {}, (_) {}, () {})))); 92 | 93 | static assert(!is(typeof(u.visit!((_) {})))); // missing void handler 94 | static assert(!is(typeof(u.visit!(() {})))); // missing value handler 95 | 96 | static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler 97 | static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler 98 | static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler 99 | static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler 100 | 101 | // TODO: error out for superfluous generic handlers 102 | //static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler 103 | } 104 | 105 | unittest { 106 | union U { 107 | Void none; 108 | int count; 109 | float length; 110 | } 111 | TaggedUnion!U u; 112 | 113 | // 114 | static assert(is(typeof(u.visit!((int) {}, (float) {}, () {})))); 115 | static assert(is(typeof(u.visit!((_) {}, () {})))); 116 | static assert(is(typeof(u.visit!((_) {}, (float) {}, () {})))); 117 | static assert(is(typeof(u.visit!((float) {}, (_) {}, () {})))); 118 | 119 | static assert(!is(typeof(u.visit!((_) {})))); // missing void handler 120 | static assert(!is(typeof(u.visit!(() {})))); // missing value handler 121 | 122 | static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler 123 | static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler 124 | static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler 125 | static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler 126 | 127 | // TODO: error out for superfluous generic handlers 128 | //static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler 129 | } 130 | 131 | unittest { 132 | // make sure that the generic handler is not instantiated with types for 133 | // which it doesn't compile 134 | class C {} 135 | union U { int i; C c; } 136 | TaggedUnion!U u; 137 | u.visit!( 138 | (C c) => c !is null, 139 | (v) { 140 | static assert(is(typeof(v) == int)); 141 | return v != 0; 142 | } 143 | ); 144 | } 145 | 146 | unittest { 147 | static struct S { 148 | Void nothing; 149 | int num; 150 | int[] nums; 151 | } 152 | alias TU = TaggedUnion!S; 153 | 154 | TU tu = [1]; 155 | tu.visit!( 156 | () { assert(false); }, 157 | (int num) { assert(false); }, 158 | (int[] num) { assert(num == [1]); } 159 | ); 160 | tu.visit!( 161 | () { assert(false); }, 162 | (const int num) { assert(false); }, 163 | (const int[] num) { assert(num == [1]); } 164 | ); 165 | tu.visit!( 166 | () { assert(false); }, 167 | (const int num) { assert(false); }, 168 | (const(int)[] num) { assert(num == [1]); } 169 | ); 170 | 171 | const(TU) tuc = TU([1]); 172 | tuc.visit!( 173 | () { assert(false); }, 174 | (int num) { assert(false); }, 175 | (const(int)[] num) { assert(num == [1]); } 176 | ); 177 | tuc.visit!( 178 | () { assert(false); }, 179 | (const(int) num) { assert(false); }, 180 | (const(int[]) num) { assert(num == [1]); } 181 | ); 182 | } 183 | 184 | 185 | /** The same as `visit`, except that failure to handle types is checked at runtime. 186 | 187 | Instead of failing to compile, `tryVisit` will throw an `Exception` if none 188 | of the handlers is able to handle the value contained in `tu`. 189 | */ 190 | template tryVisit(VISITORS...) 191 | if (VISITORS.length > 0) 192 | { 193 | auto tryVisit(TU)(auto ref TU tu) 194 | if (isInstanceOf!(TaggedUnion, TU)) 195 | { 196 | final switch (tu.kind) { 197 | static foreach (k; EnumMembers!(TU.Kind)) { 198 | case k: { 199 | static if (isUnitType!(TU.FieldTypes[k])) 200 | alias T = void; 201 | else alias T = TU.FieldTypes[k]; 202 | alias h = selectHandler!(T, VISITORS); 203 | static if (is(typeof(h) == typeof(null))) throw new Exception("Type "~T.stringof~" not handled by any visitor."); 204 | else static if (is(typeof(h) == string)) static assert(false, h); 205 | else static if (is(T == void)) return h(); 206 | else return h(tu.value!k); 207 | } 208 | } 209 | } 210 | } 211 | 212 | auto tryVisit(U)(auto ref TaggedAlgebraic!U ta) 213 | { 214 | return tryVisit(ta.get!(TaggedUnion!U)); 215 | } 216 | } 217 | 218 | /// 219 | unittest { 220 | import std.exception : assertThrown; 221 | 222 | union U { 223 | int number; 224 | string text; 225 | } 226 | alias TU = TaggedUnion!U; 227 | 228 | auto tu = TU.number(42); 229 | tu.tryVisit!((int n) { assert(n == 42); }); 230 | assertThrown(tu.tryVisit!((string s) { assert(false); })); 231 | } 232 | 233 | // repeat from TaggedUnion 234 | unittest { 235 | import std.exception : assertThrown; 236 | 237 | union U { 238 | int number; 239 | string text; 240 | } 241 | alias TA = TaggedAlgebraic!U; 242 | 243 | auto ta = TA(42); 244 | ta.tryVisit!((int n) { assert(n == 42); }); 245 | assertThrown(ta.tryVisit!((string s) { assert(false); })); 246 | } 247 | 248 | 249 | private template validateHandlers(TU, VISITORS...) 250 | { 251 | import std.traits : CopyConstness, isSomeFunction; 252 | 253 | alias ApplyConst(T) = CopyConstness!(TU, T); 254 | alias Types = staticMap!(ApplyConst, TU.FieldTypes); 255 | 256 | static foreach (int i; 0 .. VISITORS.length) { 257 | static if (isSomeFunction!(VISITORS[i])) { 258 | static assert(anySatisfy!(matchesType!(VISITORS[i]), Types), 259 | "Visitor at index "~i.stringof~" does not match any type of "~Types.stringof); 260 | } else { 261 | static assert(__traits(isTemplate, VISITORS[i]), 262 | "Visitor at index "~i.stringof~" must be a function/delegate literal: "~VISITORS[i].stringof); 263 | } 264 | } 265 | } 266 | 267 | private template matchesType(alias fun) { 268 | import std.traits : ParameterTypeTuple, isSomeFunction; 269 | 270 | template matchesType(T) { 271 | static if (isSomeFunction!fun) { 272 | alias Params = ParameterTypeTuple!fun; 273 | static if (Params.length == 0 && isUnitType!(Unqual!T)) enum matchesType = true; 274 | else static if (Params.length == 1 && isMatch!(Params[0], T)) enum matchesType = true; 275 | else enum matchesType = false; 276 | } else static if (!isUnitType!(Unqual!T)) { 277 | static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) { 278 | alias Params = ParameterTypeTuple!(fun!T); 279 | static if (Params.length == 1 && isMatch!(Params[0], T)) enum matchesType = true; 280 | else enum matchesType = false; 281 | } else enum matchesType = false; 282 | } else enum matchesType = false; 283 | } 284 | } 285 | 286 | unittest { 287 | class C {} 288 | alias mt1 = matchesType!((C c) => true); 289 | alias mt2 = matchesType!((c) { static assert(!is(typeof(c) == C)); }); 290 | static assert(mt1!C); 291 | static assert(!mt1!int); 292 | static assert(mt2!int); 293 | static assert(!mt2!C); 294 | } 295 | 296 | private template selectHandler(T, VISITORS...) 297 | { 298 | import std.traits : ParameterTypeTuple, isSomeFunction; 299 | 300 | template typedIndex(int i, int matched_index = -1) { 301 | static if (i < VISITORS.length) { 302 | alias fun = VISITORS[i]; 303 | static if (isSomeFunction!fun) { 304 | alias Params = ParameterTypeTuple!fun; 305 | static if (Params.length > 1) enum typedIndex = "Visitor at index "~i.stringof~" must not take more than one parameter."; 306 | else static if (Params.length == 0 && is(Unqual!T == void) || Params.length == 1 && isMatch!(Params[0], T)) { 307 | static if (matched_index >= 0) enum typedIndex = "Vistor at index "~i.stringof~" conflicts with visitor at index "~matched_index~"."; 308 | else enum typedIndex = typedIndex!(i+1, i); 309 | } else enum typedIndex = typedIndex!(i+1, matched_index); 310 | } else enum typedIndex = typedIndex!(i+1, matched_index); 311 | } else enum typedIndex = matched_index; 312 | } 313 | 314 | template genericIndex(int i, int matched_index = -1) { 315 | static if (i < VISITORS.length) { 316 | alias fun = VISITORS[i]; 317 | static if (!isSomeFunction!fun) { 318 | static if (!__traits(isTemplate, fun)) enum genericIndex = "Visitor at index " ~ i.stringof ~ " is neither a function, nor a template."; 319 | else static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) { 320 | static if (ParameterTypeTuple!(fun!T).length == 1) { 321 | static if (matched_index >= 0) enum genericIndex = "Only one generic visitor allowed"; 322 | else enum genericIndex = genericIndex!(i+1, i); 323 | } else enum genericIndex = "Generic visitor at index "~i.stringof~" must have a single parameter."; 324 | } else enum genericIndex = genericIndex!(i+1, i); // let this fail within the template instantiation instead of here 325 | } else enum genericIndex = genericIndex!(i+1, matched_index); 326 | } else enum genericIndex = matched_index; 327 | } 328 | 329 | enum typed_index = typedIndex!0; 330 | static if (is(T == void) || (is(typeof(typed_index) == string) && typed_index != -1)) 331 | enum generic_index = -1; 332 | else enum generic_index = genericIndex!0; 333 | 334 | static if (is(typeof(typed_index) == string)) enum selectHandler = typed_index; 335 | else static if (is(typeof(generic_index) == string)) enum selectHandler = generic_index; 336 | else static if (typed_index >= 0) alias selectHandler = VISITORS[typed_index]; 337 | else static if (generic_index >= 0) alias selectHandler = VISITORS[generic_index]; 338 | else enum selectHandler = null; 339 | } 340 | 341 | private enum isMatch(PT, T) = is(Unqual!(immutable(T)) == Unqual!(immutable(PT))) && is(T : PT); 342 | -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ueo pipefail 4 | 5 | if [ ! -z "${COVERAGE:-}" ]; then 6 | dub build --build=docs 7 | 8 | dub test -b unittest-cov 9 | wget https://codecov.io/bash -O codecov.sh 10 | bash codecov.sh 11 | else 12 | dub test 13 | 14 | if [ "x${TEST_MESON:-}" = "xtrue" ] && [ "x$(dmd --version | head -n1)" != "xDMD64 D Compiler v2.085.1" ]; then 15 | meson build && ninja -C build 16 | fi 17 | fi 18 | --------------------------------------------------------------------------------