1 module mutils.safe_union;
2 
3 import std.algorithm : max;
4 import std.conv : to;
5 import std.format : format, FormatSpec, formatValue;
6 import std.meta : staticIndexOf;
7 import std.range : put;
8 import std.traits : ForeachType, hasMember, isArray, Parameters, ReturnType;
9 
10 import mutils.serializer.binary;
11 
12 /**
13  * Union of ConTypes... 
14  * Ensures correct access with assert
15  */
16 struct SafeUnion(bool makeFirstParDefaultOne, ConTypes...) {
17 	alias FromTypes = ConTypes;
18 	static assert(FromTypes.length > 0, "Union has to have members.");
19 
20 	union {
21 		private FromTypes values;
22 	}
23 
24 	mixin(getCode!(FromTypes));
25 	//enum Types{...}    //from mixin
26 	alias Types = TypesM; // alias used to give better autocompletion in IDE-s
27 
28 	Types currentType = (makeFirstParDefaultOne) ? Types._0 : Types.none;
29 
30 	/**
31 	 * Constuctor supporting direcs assigment of Type
32 	 */
33 	this(T)(T obj) {
34 		static assert(properType!T, "Given Type is not present in union");
35 		set(obj);
36 	}
37 
38 	void opAssign(SafeUnion!(makeFirstParDefaultOne, ConTypes) obj) {
39 		this.tupleof = obj.tupleof;
40 	}
41 	//void opAssign(this);
42 	void opAssign(T)(T obj) {
43 		static assert(properType!T, "Given Type is not present in union");
44 		set(obj);
45 	}
46 
47 	/**
48 	 * returns given type with check
49 	 */
50 	@nogc nothrow auto get(T)() {
51 		static assert(properType!T, "Given Type is not present in union");
52 
53 		enum index = staticIndexOf!(T, FromTypes);
54 		assert(currentType == index, "Got type which is not currently bound.");
55 		return &values[index];
56 	}
57 
58 	/**
59 	 * Returns enum value for Type
60 	 */
61 	@nogc nothrow bool isType(T)() {
62 		static assert(properType!T, "Given Type is not present in union");
63 		enum index = staticIndexOf!(T, FromTypes);
64 		return currentType == index;
65 	}
66 
67 	/**
68 	 * Returns enum value for Type
69 	 */
70 	static Types getEnum(T)() {
71 		static assert(properType!T, "Given Type is not present in union");
72 		return cast(Types) staticIndexOf!(T, FromTypes);
73 	}
74 
75 	/**
76 	 * Sets given Type
77 	 */
78 	@nogc nothrow auto set(T)(T obj) {
79 		static assert(properType!T, "Given Type is not present in union");
80 		enum index = staticIndexOf!(T, FromTypes);
81 		currentType = cast(Types) index;
82 		values[index] = obj;
83 	}
84 
85 	auto ref apply(alias fun)() {
86 	sw:
87 		switch (currentType) {
88 			foreach (i, Type; FromTypes) {
89 		case i:
90 				return fun(values[i]);
91 			}
92 
93 		default:
94 			assert(0);
95 		}
96 	}
97 
98 	/**
99 	 * Support for serialization
100 	 */
101 	void customSerialize(Load load, Serializer, COS)(Serializer serializer, ref COS con) {
102 		auto begin = serializer.beginObject!(load)(con);
103 		scope (exit)
104 			serializer.endObject!(load)(con, begin);
105 
106 		serializer.serializeWithName!(load, "type")(currentType, con);
107 	sw:
108 		final switch (currentType) {
109 			foreach (i, Type; FromTypes) {
110 		case i:
111 				serializer.serializeWithName!(load, FromTypes[i].stringof)(values[i], con);
112 				break sw;
113 			}
114 		case Types.none:
115 			break;
116 		}
117 	}
118 
119 	/**
120 	 * Preety print
121 	 */
122 	void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) {
123 		put(sink, "SafeUnion(");
124 
125 	sw:
126 		final switch (currentType) {
127 			foreach (i, Type; FromTypes) {
128 		case i:
129 				formatValue(sink, values[i], fmt);
130 				break sw;
131 			}
132 		case Types.none:
133 			put(sink, "none");
134 			break;
135 		}
136 
137 		put(sink, ")");
138 	}
139 
140 	/**
141 	 * Checks if opDispatch supports given function
142 	 */
143 	static bool checkOpDispach(string funcName)() {
144 		bool ok = true;
145 		foreach (Type; FromTypes) {
146 			ok = ok && hasMember!(Type, funcName);
147 		}
148 		return ok;
149 	}
150 
151 	/**
152 	 * Forwards call to union member
153 	 * Works only if all union members has this function and this function has the same return type and parameter types
154 	 * Can not be made opDispatch because it somehow breakes hasMember trait
155 	 */
156 	auto call(string funcName, Args...)(auto ref Args args)
157 			if (checkOpDispach!(funcName)) {
158 		mixin("alias CompareReturnType=ReturnType!(FromTypes[0]." ~ funcName ~ ");");
159 		mixin("alias CompareParametersTypes=Parameters!(FromTypes[0]." ~ funcName ~ ");");
160 		foreach (Type; FromTypes) {
161 			mixin("enum bool typeOk=is(ReturnType!(Type." ~ funcName ~ ")==CompareReturnType);");
162 			mixin(
163 					"enum bool parametersOk=is(Parameters!(Type." ~ funcName
164 					~ ")==CompareParametersTypes);");
165 			static assert(typeOk, "Return type " ~ CompareReturnType.stringof
166 					~ " of '" ~ funcName ~ "' has to be the same in every union member.");
167 			static assert(parametersOk, "Parameter types " ~ CompareParametersTypes.stringof
168 					~ " of '" ~ funcName ~ "' have to be the same in every union member.");
169 		}
170 	sw:
171 		switch (currentType) {
172 			foreach (i, Type; FromTypes) {
173 		case i:
174 				auto val = &values[i];
175 				mixin("return val." ~ funcName ~ "(args);");
176 			}
177 		default:
178 			assert(0);
179 		}
180 	}
181 
182 package:
183 
184 	/** 
185 	 * Generates enum for stored Types
186 	 */
187 	private static string getCode(FromTypes...)() {
188 
189 		string code = "enum TypesM:ubyte{";
190 		foreach (i, Type; FromTypes) {
191 			code ~= format("_%d=%d,", i, i);
192 		}
193 		code ~= "none}";
194 		return code;
195 	}
196 
197 	/**
198 	 *  Checks if Type is in union Types
199 	 */
200 	private static bool properType(T)() {
201 		return staticIndexOf!(T, FromTypes) != -1;
202 	}
203 }
204 /// Example Usage
205 unittest {
206 	struct Triangle {
207 		int add(int a) {
208 			return a + 10;
209 		}
210 	}
211 
212 	struct Rectangle {
213 		int add(int a) {
214 			return a + 100;
215 		}
216 	}
217 
218 	static uint strangeID(T)(T obj) {
219 		static if (is(T == Triangle)) {
220 			return 123;
221 		} else static if (is(T == Rectangle)) {
222 			return 14342;
223 		} else {
224 			assert(0);
225 		}
226 	}
227 
228 	alias Shape = SafeUnion!(false, Triangle, Rectangle);
229 	Shape shp;
230 	shp.set(Triangle());
231 	assert(shp.isType!Triangle);
232 	assert(!shp.isType!Rectangle);
233 	assert(shp.call!("add")(6) == 16); //Better error messages 
234 	assert(shp.apply!strangeID == 123);
235 	//shp.get!(Rectangle);//Crash
236 	shp.set(Rectangle());
237 	assert(shp.call!("add")(6) == 106);
238 	assert(shp.apply!strangeID == 14342);
239 	shp.currentType = shp.Types.none;
240 	//shp.apply!strangeID;//Crash
241 	//shp.add(6);//Crash
242 	final switch (shp.currentType) {
243 	case shp.getEnum!Triangle:
244 		break;
245 	case Shape.getEnum!Rectangle:
246 		break;
247 	case Shape.Types.none:
248 		break;
249 	}
250 
251 }