1 /**
2 	Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos,
3 	some are dirty hacks that work only for vibe.d
4 
5 	Copyright: © 2012 Sönke Ludwig
6 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
7 	Authors: Sönke Ludwig, Михаил Страшун
8 */
9 
10 module vibe.internal.traits;
11 
12 import vibe.internal.typetuple;
13 
14 
15 /**
16 	Checks if given type is a getter function type
17 
18 	Returns: `true` if argument is a getter
19  */
20 template isPropertyGetter(T...)
21 	if (T.length == 1)
22 {
23 	import std.traits : functionAttributes, FunctionAttribute, ReturnType,
24 		isSomeFunction;
25 	static if (isSomeFunction!(T[0])) {
26 		enum isPropertyGetter =
27 			(functionAttributes!(T[0]) & FunctionAttribute.property) != 0
28 			&& !is(ReturnType!T == void);
29 	}
30 	else
31 		enum isPropertyGetter = false;
32 }
33 
34 ///
35 unittest
36 {
37 	interface Test
38 	{
39 		@property int getter();
40 		@property void setter(int);
41 		int simple();
42 	}
43 
44 	static assert(isPropertyGetter!(typeof(&Test.getter)));
45 	static assert(!isPropertyGetter!(typeof(&Test.setter)));
46 	static assert(!isPropertyGetter!(typeof(&Test.simple)));
47 	static assert(!isPropertyGetter!int);
48 }
49 
50 /**
51 	Checks if given type is a setter function type
52 
53 	Returns: `true` if argument is a setter
54  */
55 template isPropertySetter(T...)
56 	if (T.length == 1)
57 {
58 	import std.traits : functionAttributes, FunctionAttribute, ReturnType,
59 		isSomeFunction;
60 
61 	static if (isSomeFunction!(T[0])) {
62 		enum isPropertySetter =
63 			(functionAttributes!(T) & FunctionAttribute.property) != 0
64 			&& is(ReturnType!(T[0]) == void);
65 	}
66 	else
67 		enum isPropertySetter = false;
68 }
69 
70 ///
71 unittest
72 {
73 	interface Test
74 	{
75 		@property int getter();
76 		@property void setter(int);
77 		int simple();
78 	}
79 
80 	static assert(isPropertySetter!(typeof(&Test.setter)));
81 	static assert(!isPropertySetter!(typeof(&Test.getter)));
82 	static assert(!isPropertySetter!(typeof(&Test.simple)));
83 	static assert(!isPropertySetter!int);
84 }
85 
86 /**
87 	Deduces single base interface for a type. Multiple interfaces
88 	will result in compile-time error.
89 
90 	Params:
91 		T = interface or class type
92 
93 	Returns:
94 		T if it is an interface. If T is a class, interface it implements.
95 */
96 template baseInterface(T)
97 	if (is(T == interface) || is(T == class))
98 {
99 	import std.traits : InterfacesTuple;
100 
101 	static if (is(T == interface)) {
102 		alias baseInterface = T;
103 	}
104 	else
105 	{
106 		alias Ifaces = InterfacesTuple!T;
107 		static assert (
108 			Ifaces.length == 1,
109 			"Type must be either provided as an interface or implement only one interface"
110 		);
111 		alias baseInterface = Ifaces[0];
112 	}
113 }
114 
115 ///
116 unittest
117 {
118 	interface I1 { }
119 	class A : I1 { }
120 	interface I2 { }
121 	class B : I1, I2 { }
122 
123 	static assert (is(baseInterface!I1 == I1));
124 	static assert (is(baseInterface!A == I1));
125 	static assert (!is(typeof(baseInterface!B)));
126 }
127 
128 
129 /**
130 	Determins if a member is a public, non-static data field.
131 */
132 template isRWPlainField(T, string M)
133 {
134 	static if (!isRWField!(T, M)) enum isRWPlainField = false;
135 	else {
136 		//pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof);
137 		enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
138 	}
139 }
140 
141 /**
142 	Determines if a member is a public, non-static, de-facto data field.
143 
144 	In addition to plain data fields, R/W properties are also accepted.
145 */
146 template isRWField(T, string M)
147 {
148 	import std.traits;
149 	import std.typetuple;
150 
151 	static void testAssign()() {
152 		T t = void;
153 		__traits(getMember, t, M) = __traits(getMember, t, M);
154 	}
155 
156 	// reject type aliases
157 	static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false;
158 	// reject non-public members
159 	else static if (!isPublicMember!(T, M)) enum isRWField = false;
160 	// reject static members
161 	else static if (!isNonStaticMember!(T, M)) enum isRWField = false;
162 	// reject non-typed members
163 	else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false;
164 	// reject void typed members (includes templates)
165 	else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false;
166 	// reject non-assignable members
167 	else static if (!__traits(compiles, testAssign!()())) enum isRWField = false;
168 	else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) {
169 		// If M is a function, reject if not @property or returns by ref
170 		private enum FA = functionAttributes!(__traits(getMember, T, M));
171 		enum isRWField = (FA & FunctionAttribute.property) != 0;
172 	} else {
173 		enum isRWField = true;
174 	}
175 }
176 
177 unittest {
178 	import std.algorithm;
179 
180 	struct S {
181 		alias a = int; // alias
182 		int i; // plain RW field
183 		enum j = 42; // manifest constant
184 		static int k = 42; // static field
185 		private int privateJ; // private RW field
186 
187 		this(Args...)(Args args) {}
188 
189 		// read-write property (OK)
190 		@property int p1() { return privateJ; }
191 		@property void p1(int j) { privateJ = j; }
192 		// read-only property (NO)
193 		@property int p2() { return privateJ; }
194 		// write-only property (NO)
195 		@property void p3(int value) { privateJ = value; }
196 		// ref returning property (OK)
197 		@property ref int p4() return { return i; }
198 		// parameter-less template property (OK)
199 		@property ref int p5()() { return i; }
200 		// not treated as a property by DMD, so not a field
201 		@property int p6()() { return privateJ; }
202 		@property void p6(int j)() { privateJ = j; }
203 
204 		static @property int p7() { return k; }
205 		static @property void p7(int value) { k = value; }
206 
207 		ref int f1() return { return i; } // ref returning function (no field)
208 
209 		int f2(Args...)(Args args) { return i; }
210 
211 		ref int f3(Args...)(Args args) { return i; }
212 
213 		void someMethod() {}
214 
215 		ref int someTempl()() { return i; }
216 	}
217 
218 	enum plainFields = ["i"];
219 	enum fields = ["i", "p1", "p4", "p5"];
220 
221 	foreach (mem; __traits(allMembers, S)) {
222 		static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field.");
223 		else static assert(!fields.canFind(mem), mem~" not detected as field.");
224 
225 		static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field.");
226 		else static assert(!plainFields.canFind(mem), mem~" not detected as plain field.");
227 	}
228 }
229 
230 package T Tgen(T)(){ return T.init; }
231 
232 
233 /**
234 	Tests if the protection of a member is public.
235 */
236 template isPublicMember(T, string M)
237 {
238 	import std.algorithm, std.typetuple : TypeTuple;
239 
240 	static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false;
241 	else {
242 		alias MEM = TypeTuple!(__traits(getMember, T, M));
243 		enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
244 	}
245 }
246 
247 unittest {
248 	class C {
249 		int a;
250 		export int b;
251 		protected int c;
252 		private int d;
253 		package int e;
254 		void f() {}
255 		static void g() {}
256 		private void h() {}
257 		private static void i() {}
258 	}
259 
260 	static assert (isPublicMember!(C, "a"));
261 	static assert (isPublicMember!(C, "b"));
262 	static assert (!isPublicMember!(C, "c"));
263 	static assert (!isPublicMember!(C, "d"));
264 	static assert (!isPublicMember!(C, "e"));
265 	static assert (isPublicMember!(C, "f"));
266 	static assert (isPublicMember!(C, "g"));
267 	static assert (!isPublicMember!(C, "h"));
268 	static assert (!isPublicMember!(C, "i"));
269 
270 	struct S {
271 		int a;
272 		export int b;
273 		private int d;
274 		package int e;
275 	}
276 	static assert (isPublicMember!(S, "a"));
277 	static assert (isPublicMember!(S, "b"));
278 	static assert (!isPublicMember!(S, "d"));
279 	static assert (!isPublicMember!(S, "e"));
280 
281 	S s;
282 	s.a = 21;
283 	assert(s.a == 21);
284 }
285 
286 /**
287 	Tests if a member requires $(D this) to be used.
288 */
289 template isNonStaticMember(T, string M)
290 {
291 	import std.typetuple;
292 	import std.traits;
293 
294 	alias MF = TypeTuple!(__traits(getMember, T, M));
295 	static if (M.length == 0) {
296 		enum isNonStaticMember = false;
297 	} else static if (anySatisfy!(isSomeFunction, MF)) {
298 		enum isNonStaticMember = !__traits(isStaticFunction, MF);
299 	} else {
300 		enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
301 	}
302 }
303 
304 unittest { // normal fields
305 	struct S {
306 		int a;
307 		static int b;
308 		enum c = 42;
309 		void f();
310 		static void g();
311 		ref int h() return { return a; }
312 		static ref int i() { return b; }
313 	}
314 	static assert(isNonStaticMember!(S, "a"));
315 	static assert(!isNonStaticMember!(S, "b"));
316 	static assert(!isNonStaticMember!(S, "c"));
317 	static assert(isNonStaticMember!(S, "f"));
318 	static assert(!isNonStaticMember!(S, "g"));
319 	static assert(isNonStaticMember!(S, "h"));
320 	static assert(!isNonStaticMember!(S, "i"));
321 }
322 
323 unittest { // tuple fields
324 	struct S(T...) {
325 		T a;
326 		static T b;
327 	}
328 
329 	alias T = S!(int, float);
330 	auto p = T.b;
331 	static assert(isNonStaticMember!(T, "a"));
332 	static assert(!isNonStaticMember!(T, "b"));
333 
334 	alias U = S!();
335 	static assert(!isNonStaticMember!(U, "a"));
336 	static assert(!isNonStaticMember!(U, "b"));
337 }
338 
339 
340 /**
341 	Tests if a Group of types is implicitly convertible to a Group of target types.
342 */
343 bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)()
344 	if (isGroup!TYPES && isGroup!TARGET_TYPES)
345 {
346 	static assert(TYPES.expand.length == TARGET_TYPES.expand.length,
347 		"Argument count does not match.");
348 	foreach (i, V; TYPES.expand)
349 		if (!is(V : TARGET_TYPES.expand[i]))
350 			return false;
351 	return true;
352 }
353 
354 /// Test if the type $(D DG) is a correct delegate for an opApply where the
355 /// key/index is of type $(D TKEY) and the value of type $(D TVALUE).
356 template isOpApplyDg(DG, TKEY, TVALUE) {
357 	import std.traits;
358 	static if (is(DG == delegate) && is(ReturnType!DG : int)) {
359 		private alias PTT = ParameterTypeTuple!(DG);
360 		private alias PSCT = ParameterStorageClassTuple!(DG);
361 		private alias STC = ParameterStorageClass;
362 		// Just a value
363 		static if (PTT.length == 1) {
364 			enum isOpApplyDg = (is(PTT[0] == TVALUE));
365 		} else static if (PTT.length == 2) {
366 			enum isOpApplyDg = (is(PTT[0] == TKEY))
367 				&& (is(PTT[1] == TVALUE));
368 		} else
369 			enum isOpApplyDg = false;
370 	} else {
371 		enum isOpApplyDg = false;
372 	}
373 }
374 
375 unittest {
376 	static assert(isOpApplyDg!(int delegate(int, string), int, string));
377 	static assert(isOpApplyDg!(int delegate(ref int, ref string), int, string));
378 	static assert(isOpApplyDg!(int delegate(int, ref string), int, string));
379 	static assert(isOpApplyDg!(int delegate(ref int, string), int, string));
380 }
381 
382 // Synchronized statements are logically nothrow but dmd still marks them as throwing.
383 // DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704
384 import core.sync.mutex : Mutex;
385 enum synchronizedIsNothrow = __traits(compiles, (Mutex m) nothrow { synchronized(m) {} });
386 
387 
388 /// Mixin template that checks a particular aggregate type for conformance with a specific interface.
389 template validateInterfaceConformance(T, I)
390 {
391 	import vibe.internal.traits : checkInterfaceConformance;
392 	static assert(checkInterfaceConformance!(T, I) is null, checkInterfaceConformance!(T, I));
393 }
394 
395 /** Checks an aggregate type for conformance with a specific interface.
396 
397 	The value of this template is either `null`, or an error message indicating the first method
398 	of the interface that is not properly implemented by `T`.
399 */
400 template checkInterfaceConformance(T, I) {
401 	import std.meta : AliasSeq;
402 	import std.traits : FunctionAttribute, FunctionTypeOf, MemberFunctionsTuple, ParameterTypeTuple, ReturnType, functionAttributes, fullyQualifiedName;
403 
404 	alias Members = AliasSeq!(__traits(allMembers, I));
405 
406 	template checkMemberConformance(string mem) {
407 		alias Overloads = AliasSeq!(__traits(getOverloads, I, mem));
408 		template impl(size_t i) {
409 			static if (i < Overloads.length) {
410 				alias F = Overloads[i];
411 				alias FT = FunctionTypeOf!F;
412 				alias PT = ParameterTypeTuple!F;
413 				alias RT = ReturnType!F;
414 				enum attribs = functionAttributeString!F(true);
415 				static if (functionAttributes!F & FunctionAttribute.property) {
416 					static if (PT.length > 0) {
417 						static if (!is(typeof(mixin("function RT (ref T t)"~attribs~"{ return t."~mem~" = PT.init; }"))))
418 							enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement property setter `" ~
419 								mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`";
420 						else enum string impl = impl!(i+1);
421 					} else {
422 						static if (!is(typeof(mixin("function RT(ref T t)"~attribs~"{ return t."~mem~"; }"))))
423 							enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement property getter `" ~
424 								mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`";
425 						else enum string impl = impl!(i+1);
426 					}
427 				} else {
428 					static if (is(RT == void)) {
429 						static if (!is(typeof(mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }")))) {
430 							static if (mem == "write" && PT.length == 2) {
431 								auto f = mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }");
432 							}
433 							enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement method `" ~
434 								mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`";
435 						}
436 						else enum string impl = impl!(i+1);
437 					} else {
438 						static if (!is(typeof(mixin("function RT(ref T t, ref PT p)"~attribs~"{ return t."~mem~"(p); }"))))
439 							enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement method `" ~
440 								mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`";
441 						else enum string impl = impl!(i+1);
442 					}
443 				}
444 			} else enum string impl = null;
445 		}
446 		alias checkMemberConformance = impl!0;
447 	}
448 
449 	template impl(size_t i) {
450 		static if (i < Members.length) {
451 			static if (__traits(compiles, __traits(getMember, I, Members[i])))
452 				enum mc = checkMemberConformance!(Members[i]);
453 			else enum mc = null;
454 			static if (mc is null) enum impl = impl!(i+1);
455 			else enum impl = mc;
456 		} else enum string impl = null;
457 	}
458 
459 	static if (is(T : I))
460 		enum checkInterfaceConformance = null;
461 	else static if (is(T == struct) || is(T == class) || is(T == interface))
462 		enum checkInterfaceConformance = impl!0;
463 	else
464 		enum checkInterfaceConformance = "Aggregate type expected, not " ~ T.stringof;
465 }
466 
467 unittest {
468 	interface InputStream {
469 		@safe:
470 		@property bool empty() nothrow;
471 		void read(ubyte[] dst);
472 	}
473 
474 	interface OutputStream {
475 		@safe:
476 		void write(in ubyte[] bytes);
477 		void flush();
478 		void finalize();
479 		void write(InputStream stream, ulong nbytes = 0);
480 	}
481 
482 	static class OSClass : OutputStream {
483 		override void write(in ubyte[] bytes) {}
484 		override void flush() {}
485 		override void finalize() {}
486 		override void write(InputStream stream, ulong nbytes) {}
487 	}
488 
489 	mixin validateInterfaceConformance!(OSClass, OutputStream);
490 
491 	static struct OSStruct {
492 		@safe:
493 		void write(in ubyte[] bytes) {}
494 		void flush() {}
495 		void finalize() {}
496 		void write(IS)(IS stream, ulong nbytes) {}
497 	}
498 
499 	mixin validateInterfaceConformance!(OSStruct, OutputStream);
500 
501 	static struct NonOSStruct {
502 		@safe:
503 		void write(in ubyte[] bytes) {}
504 		void flush(bool) {}
505 		void finalize() {}
506 		void write(InputStream stream, ulong nbytes) {}
507 	}
508 
509 	string removeUnittestLineNumbers(string s) {
510 		import std.string;
511 
512 		string ret;
513 		size_t start;
514 		while (true)
515 		{
516 			size_t next = s.indexOf("__unittest_L", start);
517 			if (next == -1)
518 				break;
519 			size_t dot = s.indexOf('.', next);
520 			if (dot == -1)
521 				dot = s.length;
522 			else
523 				ret ~= s[start .. next + "__unittest".length];
524 			start = dot;
525 		}
526 		return ret ~ s[start .. $];
527 	}
528 
529 	static assert(removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct, OutputStream)) ==
530 		"`vibe.internal.traits.__unittest.NonOSStruct` does not implement method `flush` of type `@safe void()` from `vibe.internal.traits.__unittest.OutputStream`");
531 
532 	static struct NonOSStruct2 {
533 		void write(in ubyte[] bytes) {} // not @safe
534 		void flush(bool) {}
535 		void finalize() {}
536 		void write(InputStream stream, ulong nbytes) {}
537 	}
538 
539     // `in` used to show up as `const` / `const scope`.
540     // With dlang/dmd#11474 it shows up as `in`.
541     // Remove when support for v2.093.0 is dropped
542     static if (removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct2, OutputStream)) !=
543         "`vibe.internal.traits.__unittest.NonOSStruct2` does not implement method `write` of type `@safe void(in ubyte[] bytes)` from `vibe.internal.traits.__unittest.OutputStream`")
544     {
545         // Fallback to pre-2.092+
546         static assert(removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct2, OutputStream)) ==
547             "`vibe.internal.traits.__unittest.NonOSStruct2` does not implement method `write` of type `@safe void(const(ubyte[]) bytes)` from `vibe.internal.traits.__unittest.OutputStream`");
548     }
549 }
550 
551 string functionAttributeString(alias F)(bool restrictions_only)
552 {
553 	import std.traits : FunctionAttribute, functionAttributes;
554 
555 	auto attribs = functionAttributes!F;
556 	string ret;
557 	with (FunctionAttribute) {
558 		if (attribs & nogc) ret ~= " @nogc";
559 		if (attribs & nothrow_) ret ~= " nothrow";
560 		if (attribs & pure_) ret ~= " pure";
561 		if (attribs & safe) ret ~= " @safe";
562 		if (!restrictions_only) {
563 			if (attribs & property) ret ~= " @property";
564 			if (attribs & ref_) ret ~= " ref";
565 			if (attribs & shared_) ret ~= " shared";
566 			if (attribs & const_) ret ~= " const";
567 		}
568 	}
569 	return ret;
570 }
571 
572 string functionAttributeThisType(alias F)(string tname)
573 {
574 	import std.traits : FunctionAttribute, functionAttributes;
575 
576 	auto attribs = functionAttributes!F;
577 	string ret = tname;
578 	with (FunctionAttribute) {
579 		if (attribs & shared_) ret = "shared("~ret~")";
580 		if (attribs & const_) ret = "const("~ret~")";
581 	}
582 	return ret;
583 }