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);