vibe-core/source/vibe/internal/traits.d
2021-01-14 10:43:36 +01:00

564 lines
16 KiB
D

/**
Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos,
some are dirty hacks that work only for vibe.d
Copyright: © 2012 Sönke Ludwig
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
Authors: Sönke Ludwig, Михаил Страшун
*/
module vibe.internal.traits;
import vibe.internal.typetuple;
/**
Checks if given type is a getter function type
Returns: `true` if argument is a getter
*/
template isPropertyGetter(T...)
if (T.length == 1)
{
import std.traits : functionAttributes, FunctionAttribute, ReturnType,
isSomeFunction;
static if (isSomeFunction!(T[0])) {
enum isPropertyGetter =
(functionAttributes!(T[0]) & FunctionAttribute.property) != 0
&& !is(ReturnType!T == void);
}
else
enum isPropertyGetter = false;
}
///
unittest
{
interface Test
{
@property int getter();
@property void setter(int);
int simple();
}
static assert(isPropertyGetter!(typeof(&Test.getter)));
static assert(!isPropertyGetter!(typeof(&Test.setter)));
static assert(!isPropertyGetter!(typeof(&Test.simple)));
static assert(!isPropertyGetter!int);
}
/**
Checks if given type is a setter function type
Returns: `true` if argument is a setter
*/
template isPropertySetter(T...)
if (T.length == 1)
{
import std.traits : functionAttributes, FunctionAttribute, ReturnType,
isSomeFunction;
static if (isSomeFunction!(T[0])) {
enum isPropertySetter =
(functionAttributes!(T) & FunctionAttribute.property) != 0
&& is(ReturnType!(T[0]) == void);
}
else
enum isPropertySetter = false;
}
///
unittest
{
interface Test
{
@property int getter();
@property void setter(int);
int simple();
}
static assert(isPropertySetter!(typeof(&Test.setter)));
static assert(!isPropertySetter!(typeof(&Test.getter)));
static assert(!isPropertySetter!(typeof(&Test.simple)));
static assert(!isPropertySetter!int);
}
/**
Deduces single base interface for a type. Multiple interfaces
will result in compile-time error.
Params:
T = interface or class type
Returns:
T if it is an interface. If T is a class, interface it implements.
*/
template baseInterface(T)
if (is(T == interface) || is(T == class))
{
import std.traits : InterfacesTuple;
static if (is(T == interface)) {
alias baseInterface = T;
}
else
{
alias Ifaces = InterfacesTuple!T;
static assert (
Ifaces.length == 1,
"Type must be either provided as an interface or implement only one interface"
);
alias baseInterface = Ifaces[0];
}
}
///
unittest
{
interface I1 { }
class A : I1 { }
interface I2 { }
class B : I1, I2 { }
static assert (is(baseInterface!I1 == I1));
static assert (is(baseInterface!A == I1));
static assert (!is(typeof(baseInterface!B)));
}
/**
Determins if a member is a public, non-static data field.
*/
template isRWPlainField(T, string M)
{
static if (!isRWField!(T, M)) enum isRWPlainField = false;
else {
//pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof);
enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
}
}
/**
Determines if a member is a public, non-static, de-facto data field.
In addition to plain data fields, R/W properties are also accepted.
*/
template isRWField(T, string M)
{
import std.traits;
import std.typetuple;
static void testAssign()() {
T t = void;
__traits(getMember, t, M) = __traits(getMember, t, M);
}
// reject type aliases
static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false;
// reject non-public members
else static if (!isPublicMember!(T, M)) enum isRWField = false;
// reject static members
else static if (!isNonStaticMember!(T, M)) enum isRWField = false;
// reject non-typed members
else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false;
// reject void typed members (includes templates)
else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false;
// reject non-assignable members
else static if (!__traits(compiles, testAssign!()())) enum isRWField = false;
else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) {
// If M is a function, reject if not @property or returns by ref
private enum FA = functionAttributes!(__traits(getMember, T, M));
enum isRWField = (FA & FunctionAttribute.property) != 0;
} else {
enum isRWField = true;
}
}
unittest {
import std.algorithm;
struct S {
alias a = int; // alias
int i; // plain RW field
enum j = 42; // manifest constant
static int k = 42; // static field
private int privateJ; // private RW field
this(Args...)(Args args) {}
// read-write property (OK)
@property int p1() { return privateJ; }
@property void p1(int j) { privateJ = j; }
// read-only property (NO)
@property int p2() { return privateJ; }
// write-only property (NO)
@property void p3(int value) { privateJ = value; }
// ref returning property (OK)
@property ref int p4() return { return i; }
// parameter-less template property (OK)
@property ref int p5()() { return i; }
// not treated as a property by DMD, so not a field
@property int p6()() { return privateJ; }
@property void p6(int j)() { privateJ = j; }
static @property int p7() { return k; }
static @property void p7(int value) { k = value; }
ref int f1() return { return i; } // ref returning function (no field)
int f2(Args...)(Args args) { return i; }
ref int f3(Args...)(Args args) { return i; }
void someMethod() {}
ref int someTempl()() { return i; }
}
enum plainFields = ["i"];
enum fields = ["i", "p1", "p4", "p5"];
foreach (mem; __traits(allMembers, S)) {
static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field.");
else static assert(!fields.canFind(mem), mem~" not detected as field.");
static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field.");
else static assert(!plainFields.canFind(mem), mem~" not detected as plain field.");
}
}
package T Tgen(T)(){ return T.init; }
/**
Tests if the protection of a member is public.
*/
template isPublicMember(T, string M)
{
import std.algorithm, std.typetuple : TypeTuple;
static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false;
else {
alias MEM = TypeTuple!(__traits(getMember, T, M));
enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
}
}
unittest {
class C {
int a;
export int b;
protected int c;
private int d;
package int e;
void f() {}
static void g() {}
private void h() {}
private static void i() {}
}
static assert (isPublicMember!(C, "a"));
static assert (isPublicMember!(C, "b"));
static assert (!isPublicMember!(C, "c"));
static assert (!isPublicMember!(C, "d"));
static assert (!isPublicMember!(C, "e"));
static assert (isPublicMember!(C, "f"));
static assert (isPublicMember!(C, "g"));
static assert (!isPublicMember!(C, "h"));
static assert (!isPublicMember!(C, "i"));
struct S {
int a;
export int b;
private int d;
package int e;
}
static assert (isPublicMember!(S, "a"));
static assert (isPublicMember!(S, "b"));
static assert (!isPublicMember!(S, "d"));
static assert (!isPublicMember!(S, "e"));
S s;
s.a = 21;
assert(s.a == 21);
}
/**
Tests if a member requires $(D this) to be used.
*/
template isNonStaticMember(T, string M)
{
import std.typetuple;
import std.traits;
alias MF = TypeTuple!(__traits(getMember, T, M));
static if (M.length == 0) {
enum isNonStaticMember = false;
} else static if (anySatisfy!(isSomeFunction, MF)) {
enum isNonStaticMember = !__traits(isStaticFunction, MF);
} else {
enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
}
}
unittest { // normal fields
struct S {
int a;
static int b;
enum c = 42;
void f();
static void g();
ref int h() return { return a; }
static ref int i() { return b; }
}
static assert(isNonStaticMember!(S, "a"));
static assert(!isNonStaticMember!(S, "b"));
static assert(!isNonStaticMember!(S, "c"));
static assert(isNonStaticMember!(S, "f"));
static assert(!isNonStaticMember!(S, "g"));
static assert(isNonStaticMember!(S, "h"));
static assert(!isNonStaticMember!(S, "i"));
}
unittest { // tuple fields
struct S(T...) {
T a;
static T b;
}
alias T = S!(int, float);
auto p = T.b;
static assert(isNonStaticMember!(T, "a"));
static assert(!isNonStaticMember!(T, "b"));
alias U = S!();
static assert(!isNonStaticMember!(U, "a"));
static assert(!isNonStaticMember!(U, "b"));
}
/**
Tests if a Group of types is implicitly convertible to a Group of target types.
*/
bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)()
if (isGroup!TYPES && isGroup!TARGET_TYPES)
{
static assert(TYPES.expand.length == TARGET_TYPES.expand.length,
"Argument count does not match.");
foreach (i, V; TYPES.expand)
if (!is(V : TARGET_TYPES.expand[i]))
return false;
return true;
}
/// Test if the type $(D DG) is a correct delegate for an opApply where the
/// key/index is of type $(D TKEY) and the value of type $(D TVALUE).
template isOpApplyDg(DG, TKEY, TVALUE) {
import std.traits;
static if (is(DG == delegate) && is(ReturnType!DG : int)) {
private alias PTT = ParameterTypeTuple!(DG);
private alias PSCT = ParameterStorageClassTuple!(DG);
private alias STC = ParameterStorageClass;
// Just a value
static if (PTT.length == 1) {
enum isOpApplyDg = (is(PTT[0] == TVALUE));
} else static if (PTT.length == 2) {
enum isOpApplyDg = (is(PTT[0] == TKEY))
&& (is(PTT[1] == TVALUE));
} else
enum isOpApplyDg = false;
} else {
enum isOpApplyDg = false;
}
}
unittest {
static assert(isOpApplyDg!(int delegate(int, string), int, string));
static assert(isOpApplyDg!(int delegate(ref int, ref string), int, string));
static assert(isOpApplyDg!(int delegate(int, ref string), int, string));
static assert(isOpApplyDg!(int delegate(ref int, string), int, string));
}
// Synchronized statements are logically nothrow but dmd still marks them as throwing.
// DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704
import core.sync.mutex : Mutex;
enum synchronizedIsNothrow = __traits(compiles, (Mutex m) nothrow { synchronized(m) {} });
/// Mixin template that checks a particular aggregate type for conformance with a specific interface.
template validateInterfaceConformance(T, I)
{
import vibe.internal.traits : checkInterfaceConformance;
static assert(checkInterfaceConformance!(T, I) is null, checkInterfaceConformance!(T, I));
}
/** Checks an aggregate type for conformance with a specific interface.
The value of this template is either `null`, or an error message indicating the first method
of the interface that is not properly implemented by `T`.
*/
template checkInterfaceConformance(T, I) {
import std.meta : AliasSeq;
import std.traits : FunctionAttribute, FunctionTypeOf, MemberFunctionsTuple, ParameterTypeTuple, ReturnType, functionAttributes;
alias Members = AliasSeq!(__traits(allMembers, I));
template checkMemberConformance(string mem) {
alias Overloads = AliasSeq!(__traits(getOverloads, I, mem));
template impl(size_t i) {
static if (i < Overloads.length) {
alias F = Overloads[i];
alias FT = FunctionTypeOf!F;
alias PT = ParameterTypeTuple!F;
alias RT = ReturnType!F;
enum attribs = functionAttributeString!F(true);
static if (functionAttributes!F & FunctionAttribute.property) {
static if (PT.length > 0) {
static if (!is(typeof(mixin("function RT (ref T t)"~attribs~"{ return t."~mem~" = PT.init; }"))))
enum impl = "`" ~ T.stringof ~ "` does not implement property setter `" ~
mem ~ "` of type `" ~ FT.stringof ~ "`";
else enum string impl = impl!(i+1);
} else {
static if (!is(typeof(mixin("function RT(ref T t)"~attribs~"{ return t."~mem~"; }"))))
enum impl = "`" ~ T.stringof ~ "` does not implement property getter `" ~
mem ~ "` of type `" ~ FT.stringof ~ "`";
else enum string impl = impl!(i+1);
}
} else {
static if (is(RT == void)) {
static if (!is(typeof(mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }")))) {
static if (mem == "write" && PT.length == 2) {
auto f = mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }");
}
enum impl = "`" ~ T.stringof ~ "` does not implement method `" ~
mem ~ "` of type `" ~ FT.stringof ~ "`";
}
else enum string impl = impl!(i+1);
} else {
static if (!is(typeof(mixin("function RT(ref T t, ref PT p)"~attribs~"{ return t."~mem~"(p); }"))))
enum impl = "`" ~ T.stringof ~ "` does not implement method `" ~
mem ~ "` of type `" ~ FT.stringof ~ "`";
else enum string impl = impl!(i+1);
}
}
} else enum string impl = null;
}
alias checkMemberConformance = impl!0;
}
template impl(size_t i) {
static if (i < Members.length) {
static if (__traits(compiles, __traits(getMember, I, Members[i])))
enum mc = checkMemberConformance!(Members[i]);
else enum mc = null;
static if (mc is null) enum impl = impl!(i+1);
else enum impl = mc;
} else enum string impl = null;
}
static if (is(T : I))
enum checkInterfaceConformance = null;
else static if (is(T == struct) || is(T == class) || is(T == interface))
enum checkInterfaceConformance = impl!0;
else
enum checkInterfaceConformance = "Aggregate type expected, not " ~ T.stringof;
}
unittest {
interface InputStream {
@safe:
@property bool empty() nothrow;
void read(ubyte[] dst);
}
interface OutputStream {
@safe:
void write(in ubyte[] bytes);
void flush();
void finalize();
void write(InputStream stream, ulong nbytes = 0);
}
static class OSClass : OutputStream {
override void write(in ubyte[] bytes) {}
override void flush() {}
override void finalize() {}
override void write(InputStream stream, ulong nbytes) {}
}
mixin validateInterfaceConformance!(OSClass, OutputStream);
static struct OSStruct {
@safe:
void write(in ubyte[] bytes) {}
void flush() {}
void finalize() {}
void write(IS)(IS stream, ulong nbytes) {}
}
mixin validateInterfaceConformance!(OSStruct, OutputStream);
static struct NonOSStruct {
@safe:
void write(in ubyte[] bytes) {}
void flush(bool) {}
void finalize() {}
void write(InputStream stream, ulong nbytes) {}
}
static assert(checkInterfaceConformance!(NonOSStruct, OutputStream) ==
"`NonOSStruct` does not implement method `flush` of type `@safe void()`");
static struct NonOSStruct2 {
void write(in ubyte[] bytes) {} // not @safe
void flush(bool) {}
void finalize() {}
void write(InputStream stream, ulong nbytes) {}
}
// `in` used to show up as `const` / `const scope`.
// With dlang/dmd#11474 it shows up as `in`.
// Remove when support for v2.093.0 is dropped
static if (checkInterfaceConformance!(NonOSStruct2, OutputStream) !=
"`NonOSStruct2` does not implement method `write` of type `@safe void(in ubyte[] bytes)`")
{
// Fallback to pre-2.092+
static assert(checkInterfaceConformance!(NonOSStruct2, OutputStream) ==
"`NonOSStruct2` does not implement method `write` of type `@safe void(const(ubyte[]) bytes)`");
}
}
string functionAttributeString(alias F)(bool restrictions_only)
{
import std.traits : FunctionAttribute, functionAttributes;
auto attribs = functionAttributes!F;
string ret;
with (FunctionAttribute) {
if (attribs & nogc) ret ~= " @nogc";
if (attribs & nothrow_) ret ~= " nothrow";
if (attribs & pure_) ret ~= " pure";
if (attribs & safe) ret ~= " @safe";
if (!restrictions_only) {
if (attribs & property) ret ~= " @property";
if (attribs & ref_) ret ~= " ref";
if (attribs & shared_) ret ~= " shared";
if (attribs & const_) ret ~= " const";
}
}
return ret;
}
string functionAttributeThisType(alias F)(string tname)
{
import std.traits : FunctionAttribute, functionAttributes;
auto attribs = functionAttributes!F;
string ret = tname;
with (FunctionAttribute) {
if (attribs & shared_) ret = "shared("~ret~")";
if (attribs & const_) ret = "const("~ret~")";
}
return ret;
}