9ae3a2a263
As explained in details in dlang/dmd#11000 and dlang/dmd#11474, 'in' has been for a very long time lowered to simply 'const', or 'scope const' when v2.092.0's '-preview=in' switch is used. DMD PR 11474 aims to change this, so 'in' is not (visibly) lowered, and shows up in any user-visible string. This includes header generation, error messages, and stringof. Since this code was testing stringof directly, it is affected by the change. To support testing of both aforementioned DMD PRs, the change is not tied to a version, but uses the (soon-to-be) old way as a fallback.
559 lines
16 KiB
D
559 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 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 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 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);
|
|
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;
|
|
}
|