aboutsummaryrefslogtreecommitdiff
path: root/libphobos/src/std
diff options
context:
space:
mode:
Diffstat (limited to 'libphobos/src/std')
-rw-r--r--libphobos/src/std/algorithm/searching.d82
-rw-r--r--libphobos/src/std/array.d59
-rw-r--r--libphobos/src/std/bitmanip.d23
-rw-r--r--libphobos/src/std/container/dlist.d25
-rw-r--r--libphobos/src/std/conv.d11
-rw-r--r--libphobos/src/std/digest/package.d342
-rw-r--r--libphobos/src/std/format/internal/write.d33
-rw-r--r--libphobos/src/std/format/read.d115
-rw-r--r--libphobos/src/std/logger/core.d4
-rw-r--r--libphobos/src/std/logger/filelogger.d16
-rw-r--r--libphobos/src/std/logger/package.d2
-rw-r--r--libphobos/src/std/numeric.d25
-rw-r--r--libphobos/src/std/process.d11
-rw-r--r--libphobos/src/std/socket.d22
-rw-r--r--libphobos/src/std/sumtype.d182
-rw-r--r--libphobos/src/std/traits.d38
-rw-r--r--libphobos/src/std/typecons.d345
-rw-r--r--libphobos/src/std/windows/syserror.d1
18 files changed, 1202 insertions, 134 deletions
diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d
index 42a9df5..b7119d2 100644
--- a/libphobos/src/std/algorithm/searching.d
+++ b/libphobos/src/std/algorithm/searching.d
@@ -3735,6 +3735,47 @@ if (isInputRange!Range && !isInfinite!Range &&
assert(arr.minElement!"a.val".val == 0);
}
+// https://issues.dlang.org/show_bug.cgi?id=24827
+@safe unittest
+{
+ static struct S
+ {
+ int i;
+ bool destroyed;
+
+ this(int i) @safe
+ {
+ this.i = i;
+ }
+
+ ~this() @safe
+ {
+ destroyed = true;
+ }
+
+ bool opEquals()(auto ref S rhs)
+ {
+ return this.i == rhs.i;
+ }
+
+ int opCmp()(auto ref S rhs)
+ {
+ if (this.i < rhs.i)
+ return -1;
+
+ return this.i == rhs.i ? 0 : 1;
+ }
+
+ @safe invariant
+ {
+ assert(!destroyed);
+ }
+ }
+
+ auto arr = [S(19), S(2), S(145), S(7)];
+ assert(minElement(arr) == S(2));
+}
+
/**
Iterates the passed range and returns the maximal element.
A custom mapping function can be passed to `map`.
@@ -3888,6 +3929,47 @@ if (isInputRange!Range && !isInfinite!Range &&
assert(arr[0].getI == 2);
}
+// https://issues.dlang.org/show_bug.cgi?id=24827
+@safe unittest
+{
+ static struct S
+ {
+ int i;
+ bool destroyed;
+
+ this(int i) @safe
+ {
+ this.i = i;
+ }
+
+ ~this() @safe
+ {
+ destroyed = true;
+ }
+
+ bool opEquals()(auto ref S rhs)
+ {
+ return this.i == rhs.i;
+ }
+
+ int opCmp()(auto ref S rhs)
+ {
+ if (this.i < rhs.i)
+ return -1;
+
+ return this.i == rhs.i ? 0 : 1;
+ }
+
+ @safe invariant
+ {
+ assert(!destroyed);
+ }
+ }
+
+ auto arr = [S(19), S(2), S(145), S(7)];
+ assert(maxElement(arr) == S(145));
+}
+
// minPos
/**
Computes a subrange of `range` starting at the first occurrence of `range`'s
diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d
index acd5311..3313dbb 100644
--- a/libphobos/src/std/array.d
+++ b/libphobos/src/std/array.d
@@ -3639,6 +3639,7 @@ if (isDynamicArray!A)
}
else
{
+ import core.stdc.string : memcpy, memset;
// Time to reallocate.
// We need to almost duplicate what's in druntime, except we
// have better access to the capacity field.
@@ -3650,6 +3651,15 @@ if (isDynamicArray!A)
if (u)
{
// extend worked, update the capacity
+ // if the type has indirections, we need to zero any new
+ // data that we requested, as the existing data may point
+ // at large unused blocks.
+ static if (hasIndirections!T)
+ {
+ immutable addedSize = u - (_data.capacity * T.sizeof);
+ () @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }();
+ }
+
_data.capacity = u / T.sizeof;
return;
}
@@ -3665,10 +3675,20 @@ if (isDynamicArray!A)
auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))();
_data.capacity = bi.size / T.sizeof;
- import core.stdc.string : memcpy;
if (len)
() @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }();
+
_data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])();
+
+ // we requested new bytes that are not in the existing
+ // data. If T has pointers, then this new data could point at stale
+ // objects from the last time this block was allocated. Zero that
+ // new data out, it may point at large unused blocks!
+ static if (hasIndirections!T)
+ () @trusted {
+ memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof);
+ }();
+
_data.tryExtendBlock = true;
// leave the old data, for safety reasons
}
@@ -4047,6 +4067,43 @@ if (isDynamicArray!A)
app2.toString();
}
+// https://issues.dlang.org/show_bug.cgi?id=24856
+@system unittest
+{
+ import core.memory : GC;
+ import std.stdio : writeln;
+ import std.algorithm.searching : canFind;
+ GC.disable();
+ scope(exit) GC.enable();
+ void*[] freeme;
+ // generate some poison blocks to allocate from.
+ auto poison = cast(void*) 0xdeadbeef;
+ foreach (i; 0 .. 10)
+ {
+ auto blk = new void*[7];
+ blk[] = poison;
+ freeme ~= blk.ptr;
+ }
+
+ foreach (p; freeme)
+ GC.free(p);
+
+ int tests = 0;
+ foreach (i; 0 .. 10)
+ {
+ Appender!(void*[]) app;
+ app.put(null);
+ // if not a realloc of one of the deadbeef pointers, continue
+ if (!freeme.canFind(app.data.ptr))
+ continue;
+ ++tests;
+ assert(!app.data.ptr[0 .. app.capacity].canFind(poison), "Appender not zeroing data!");
+ }
+ // just notify in the log whether this test actually could be done.
+ if (tests == 0)
+ writeln("WARNING: test of Appender zeroing did not occur");
+}
+
//Calculates an efficient growth scheme based on the old capacity
//of data, and the minimum requested capacity.
//arg curLen: The current length
diff --git a/libphobos/src/std/bitmanip.d b/libphobos/src/std/bitmanip.d
index 15211a3..f8a97df 100644
--- a/libphobos/src/std/bitmanip.d
+++ b/libphobos/src/std/bitmanip.d
@@ -106,7 +106,7 @@ private template createAccessors(
enum RightShiftOp = ">>>=";
}
- static if (is(T == bool))
+ static if (is(T : bool))
{
enum createAccessors =
// getter
@@ -4676,3 +4676,24 @@ if (isIntegral!T)
foreach (i; 0 .. 63)
assert(bitsSet(1UL << i).equal([i]));
}
+
+// Fix https://issues.dlang.org/show_bug.cgi?id=24095
+@safe @nogc pure unittest
+{
+ enum Bar : bool
+ {
+ a,
+ b,
+ }
+
+ struct Foo
+ {
+ mixin(bitfields!(Bar, "bar", 1, ubyte, "", 7,));
+ }
+
+ Foo foo;
+ foo.bar = Bar.a;
+ assert(foo.bar == Bar.a);
+ foo.bar = Bar.b;
+ assert(foo.bar == Bar.b);
+}
diff --git a/libphobos/src/std/container/dlist.d b/libphobos/src/std/container/dlist.d
index 728aacd..8f7df10 100644
--- a/libphobos/src/std/container/dlist.d
+++ b/libphobos/src/std/container/dlist.d
@@ -185,6 +185,7 @@ Implements a doubly-linked list.
struct DList(T)
{
import std.range : Take;
+ import std.traits : isMutable;
/*
A Node with a Payload. A PayNode.
@@ -220,7 +221,10 @@ struct DList(T)
{
import std.algorithm.mutation : move;
- return (new PayNode(BaseNode(prev, next), move(arg))).asBaseNode();
+ static if (isMutable!Stuff)
+ return (new PayNode(BaseNode(prev, next), move(arg))).asBaseNode();
+ else
+ return (new PayNode(BaseNode(prev, next), arg)).asBaseNode();
}
void initialize() nothrow @safe pure
@@ -1149,3 +1153,22 @@ private:
list.removeFront();
assert(list[].walkLength == 0);
}
+
+// https://issues.dlang.org/show_bug.cgi?id=24637
+@safe unittest
+{
+ import std.algorithm.comparison : equal;
+
+ struct A
+ {
+ int c;
+ }
+
+ DList!A B;
+ B.insert(A(1));
+ assert(B[].equal([A(1)]));
+
+ const a = A(3);
+ B.insert(a);
+ assert(B[].equal([A(1), A(3)]));
+}
diff --git a/libphobos/src/std/conv.d b/libphobos/src/std/conv.d
index 9c9d8db..5e0165c 100644
--- a/libphobos/src/std/conv.d
+++ b/libphobos/src/std/conv.d
@@ -2560,9 +2560,6 @@ Lerr:
string s1 = "123";
auto a1 = parse!(int, string, Yes.doCount)(s1);
assert(a1.data == 123 && a1.count == 3);
-
- // parse only accepts lvalues
- static assert(!__traits(compiles, parse!int("123")));
}
///
@@ -5611,6 +5608,14 @@ Params:
Returns:
a `string`, a `wstring` or a `dstring`, according to the type of hexData.
+
+See_Also:
+ Use $(REF fromHexString, std, digest) for run time conversions.
+ Note, these functions are not drop-in replacements and have different
+ input requirements.
+ This template inherits its data syntax from builtin
+ $(LINK2 $(ROOT_DIR)spec/lex.html#hex_string, hex strings).
+ See $(REF fromHexString, std, digest) for its own respective requirements.
*/
template hexString(string hexData)
if (hexData.isHexLiteral)
diff --git a/libphobos/src/std/digest/package.d b/libphobos/src/std/digest/package.d
index ea3738b..8274680 100644
--- a/libphobos/src/std/digest/package.d
+++ b/libphobos/src/std/digest/package.d
@@ -1212,3 +1212,345 @@ if (isInputRange!R1 && isInputRange!R2 && !isInfinite!R1 && !isInfinite!R2 &&
assert(!secureEqual(hex1, hex2));
}
}
+
+/**
+ * Validates a hex string.
+ *
+ * Checks whether all characters following an optional "0x" suffix
+ * are valid hexadecimal digits.
+ *
+ * Params:
+ * hex = hexdecimal encoded byte array
+ * Returns:
+ * true = if valid
+ */
+bool isHexString(String)(String hex) @safe pure nothrow @nogc
+if (isSomeString!String)
+{
+ import std.ascii : isHexDigit;
+
+ if ((hex.length >= 2) && (hex[0 .. 2] == "0x"))
+ {
+ hex = hex[2 .. $];
+ }
+
+ foreach (digit; hex)
+ {
+ if (!digit.isHexDigit)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+///
+@safe unittest
+{
+ assert(isHexString("0x0123456789ABCDEFabcdef"));
+ assert(isHexString("0123456789ABCDEFabcdef"));
+ assert(!isHexString("g"));
+ assert(!isHexString("#"));
+}
+
+/**
+ * Converts a hex text string to a range of bytes.
+ *
+ * The input to this function MUST be valid.
+ * $(REF isHexString, std, digest) can be used to check for this if needed.
+ *
+ * Params:
+ * hex = String representation of a hexdecimal-encoded byte array.
+ * Returns:
+ * A forward range of bytes.
+ */
+auto fromHexStringAsRange(String)(String hex) @safe pure nothrow @nogc
+if (isSomeString!String)
+{
+ return HexStringDecoder!String(hex);
+}
+
+///
+@safe unittest
+{
+ import std.range.primitives : ElementType, isForwardRange;
+ import std.traits : ReturnType;
+
+ // The decoder implements a forward range.
+ static assert(isForwardRange!(ReturnType!(fromHexStringAsRange!string)));
+ static assert(isForwardRange!(ReturnType!(fromHexStringAsRange!wstring)));
+ static assert(isForwardRange!(ReturnType!(fromHexStringAsRange!dstring)));
+
+ // The element type of the range is always `ubyte`.
+ static assert(
+ is(ElementType!(ReturnType!(fromHexStringAsRange!string)) == ubyte)
+ );
+ static assert(
+ is(ElementType!(ReturnType!(fromHexStringAsRange!wstring)) == ubyte)
+ );
+ static assert(
+ is(ElementType!(ReturnType!(fromHexStringAsRange!dstring)) == ubyte)
+ );
+}
+
+@safe unittest
+{
+ import std.array : staticArray;
+
+ // `staticArray` consumes the range returned by `fromHexStringAsRange`.
+ assert("0x0000ff".fromHexStringAsRange.staticArray!3 == [0, 0, 0xFF]);
+ assert("0x0000ff"w.fromHexStringAsRange.staticArray!3 == [0, 0, 0xFF]);
+ assert("0x0000ff"d.fromHexStringAsRange.staticArray!3 == [0, 0, 0xFF]);
+ assert("0xff12ff".fromHexStringAsRange.staticArray!1 == [0xFF]);
+ assert("0x12ff".fromHexStringAsRange.staticArray!2 == [0x12, 255]);
+ assert(
+ "0x3AaAA".fromHexStringAsRange.staticArray!4 == [0x3, 0xAA, 0xAA, 0x00]
+ );
+}
+
+/**
+ * Converts a hex text string to a range of bytes.
+ *
+ * Params:
+ * hex = String representation of a hexdecimal-encoded byte array.
+ * Returns:
+ * An newly allocated array of bytes.
+ * Throws:
+ * Exception on invalid input.
+ * Example:
+ * ---
+ * ubyte[] dby = "0xBA".fromHexString;
+ * ---
+ * See_Also:
+ * $(REF fromHexString, std, digest) for a range version of the function.
+ */
+ubyte[] fromHexString(String)(String hex) @safe pure
+if (isSomeString!String)
+{
+ // This function is trivial, yet necessary for consistency.
+ // It provides a similar API to its `toHexString` counterpart.
+
+ if (!hex.isHexString)
+ {
+ import std.conv : text;
+
+ throw new Exception(
+ "The provided character sequence `"
+ ~ hex.text
+ ~ "` is not a valid hex string."
+ );
+ }
+
+ if ((hex.length >= 2) && (hex[0 .. 2] == "0x"))
+ {
+ hex = hex[2 .. $];
+ }
+
+ auto decoder = HexStringDecoder!String(hex);
+ auto result = new ubyte[](decoder.length);
+
+ size_t idx = 0;
+ foreach (b; decoder)
+ {
+ result[idx++] = b;
+ }
+ return result;
+}
+
+///
+@safe unittest
+{
+ // Single byte
+ assert("0xff".fromHexString == [255]);
+ assert("0xff"w.fromHexString == [255]);
+ assert("0xff"d.fromHexString == [255]);
+ assert("0xC0".fromHexString == [192]);
+ assert("0x00".fromHexString == [0]);
+
+ // Nothing
+ assert("".fromHexString == []);
+ assert(""w.fromHexString == []);
+ assert(""d.fromHexString == []);
+
+ // Nothing but a prefix
+ assert("0x".fromHexString == []);
+ assert("0x"w.fromHexString == []);
+ assert("0x"d.fromHexString == []);
+
+ // Half a byte
+ assert("0x1".fromHexString == [0x01]);
+ assert("0x1"w.fromHexString == [0x01]);
+ assert("0x1"d.fromHexString == [0x01]);
+
+ // Mixed case is fine.
+ assert("0xAf".fromHexString == [0xAF]);
+ assert("0xaF".fromHexString == [0xAF]);
+
+ // Multiple bytes
+ assert("0xfff".fromHexString == [0x0F, 0xFF]);
+ assert("0x123AaAa".fromHexString == [0x01, 0x23, 0xAA, 0xAA]);
+ assert("EBBBBF".fromHexString == [0xEB, 0xBB, 0xBF]);
+
+ // md5 sum
+ assert("d41d8cd98f00b204e9800998ecf8427e".fromHexString == [
+ 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04,
+ 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E,
+ ]);
+}
+
+///
+@safe unittest
+{
+ // Cycle self-test
+ const ubyte[] initial = [0x00, 0x12, 0x34, 0xEB];
+ assert(initial == initial.toHexString().fromHexString());
+}
+
+private ubyte hexDigitToByte(dchar hexDigit) @safe pure nothrow @nogc
+{
+ static int hexDigitToByteImpl(dchar hexDigit)
+ {
+ if (hexDigit >= '0' && hexDigit <= '9')
+ {
+ return hexDigit - '0';
+ }
+ else if (hexDigit >= 'A' && hexDigit <= 'F')
+ {
+ return hexDigit - 'A' + 10;
+ }
+ else if (hexDigit >= 'a' && hexDigit <= 'f')
+ {
+ return hexDigit - 'a' + 10;
+ }
+
+ assert(false, "Cannot convert invalid hex digit.");
+ }
+
+ return hexDigitToByteImpl(hexDigit) & 0xFF;
+}
+
+@safe unittest
+{
+ assert(hexDigitToByte('0') == 0x0);
+ assert(hexDigitToByte('9') == 0x9);
+ assert(hexDigitToByte('a') == 0xA);
+ assert(hexDigitToByte('b') == 0xB);
+ assert(hexDigitToByte('A') == 0xA);
+ assert(hexDigitToByte('C') == 0xC);
+}
+
+private struct HexStringDecoder(String)
+if (isSomeString!String)
+{
+ String hex;
+ ubyte front;
+ bool empty;
+
+ this(String hex)
+ {
+ if ((hex.length >= 2) && (hex[0 .. 2] == "0x"))
+ {
+ hex = hex[2 .. $];
+ }
+
+ if (hex.length == 0)
+ {
+ empty = true;
+ return;
+ }
+
+ const oddInputLength = (hex.length % 2 == 1);
+
+ if (oddInputLength)
+ {
+ front = hexDigitToByte(hex[0]);
+ hex = hex[1 .. $];
+ }
+ else
+ {
+ front = cast(ubyte)(hexDigitToByte(hex[0]) << 4 | hexDigitToByte(hex[1]));
+ hex = hex[2 .. $];
+ }
+
+ this.hex = hex;
+ }
+
+ void popFront()
+ {
+ if (hex.length == 0)
+ {
+ empty = true;
+ return;
+ }
+
+ front = cast(ubyte)(hexDigitToByte(hex[0]) << 4 | hexDigitToByte(hex[1]));
+ hex = hex[2 .. $];
+ }
+
+ typeof(this) save()
+ {
+ return this;
+ }
+
+ size_t length() const
+ {
+ if (this.empty)
+ {
+ return 0;
+ }
+
+ // current front + remainder
+ return 1 + (hex.length >> 1);
+ }
+}
+
+@safe unittest
+{
+ auto decoder = HexStringDecoder!string("");
+ assert(decoder.empty);
+ assert(decoder.length == 0);
+
+ decoder = HexStringDecoder!string("0x");
+ assert(decoder.empty);
+ assert(decoder.length == 0);
+}
+
+@safe unittest
+{
+ auto decoder = HexStringDecoder!string("0x0077FF");
+ assert(!decoder.empty);
+ assert(decoder.length == 3);
+ assert(decoder.front == 0x00);
+
+ decoder.popFront();
+ assert(!decoder.empty);
+ assert(decoder.length == 2);
+ assert(decoder.front == 0x77);
+
+ decoder.popFront();
+ assert(!decoder.empty);
+ assert(decoder.length == 1);
+ assert(decoder.front == 0xFF);
+
+ decoder.popFront();
+ assert(decoder.length == 0);
+ assert(decoder.empty);
+}
+
+@safe unittest
+{
+ auto decoder = HexStringDecoder!string("0x7FF");
+ assert(!decoder.empty);
+ assert(decoder.length == 2);
+ assert(decoder.front == 0x07);
+
+ decoder.popFront();
+ assert(!decoder.empty);
+ assert(decoder.length == 1);
+ assert(decoder.front == 0xFF);
+
+ decoder.popFront();
+ assert(decoder.length == 0);
+ assert(decoder.empty);
+}
diff --git a/libphobos/src/std/format/internal/write.d b/libphobos/src/std/format/internal/write.d
index 8b60565..6fd468d 100644
--- a/libphobos/src/std/format/internal/write.d
+++ b/libphobos/src/std/format/internal/write.d
@@ -1839,24 +1839,26 @@ template hasToString(T, Char)
else static if (is(typeof(
(T val) {
const FormatSpec!Char f;
- static struct S {void put(scope Char s){}}
+ static struct S
+ {
+ @disable this(this);
+ void put(scope Char s){}
+ }
S s;
val.toString(s, f);
- static assert(!__traits(compiles, val.toString(s, FormatSpec!Char())),
- "force toString to take parameters by ref");
- static assert(!__traits(compiles, val.toString(S(), f)),
- "force toString to take parameters by ref");
})))
{
enum hasToString = HasToStringResult.customPutWriterFormatSpec;
}
else static if (is(typeof(
(T val) {
- static struct S {void put(scope Char s){}}
+ static struct S
+ {
+ @disable this(this);
+ void put(scope Char s){}
+ }
S s;
val.toString(s);
- static assert(!__traits(compiles, val.toString(S())),
- "force toString to take parameters by ref");
})))
{
enum hasToString = HasToStringResult.customPutWriter;
@@ -1996,9 +1998,10 @@ template hasToString(T, Char)
static assert(hasToString!(G, char) == customPutWriter);
static assert(hasToString!(H, char) == customPutWriterFormatSpec);
static assert(hasToString!(I, char) == customPutWriterFormatSpec);
- static assert(hasToString!(J, char) == hasSomeToString);
+ static assert(hasToString!(J, char) == hasSomeToString
+ || hasToString!(J, char) == constCharSinkFormatSpec); // depends on -preview=rvaluerefparam
static assert(hasToString!(K, char) == constCharSinkFormatSpec);
- static assert(hasToString!(L, char) == none);
+ static assert(hasToString!(L, char) == customPutWriterFormatSpec);
static if (hasPreviewIn)
{
static assert(hasToString!(M, char) == inCharSinkFormatSpec);
@@ -2105,9 +2108,10 @@ template hasToString(T, Char)
static assert(hasToString!(G, char) == customPutWriter);
static assert(hasToString!(H, char) == customPutWriterFormatSpec);
static assert(hasToString!(I, char) == customPutWriterFormatSpec);
- static assert(hasToString!(J, char) == hasSomeToString);
+ static assert(hasToString!(J, char) == hasSomeToString
+ || hasToString!(J, char) == constCharSinkFormatSpec); // depends on -preview=rvaluerefparam
static assert(hasToString!(K, char) == constCharSinkFormatSpec);
- static assert(hasToString!(L, char) == none);
+ static assert(hasToString!(L, char) == HasToStringResult.customPutWriterFormatSpec);
static if (hasPreviewIn)
{
static assert(hasToString!(M, char) == inCharSinkFormatSpec);
@@ -2125,9 +2129,10 @@ template hasToString(T, Char)
static assert(hasToString!(inout(G), char) == customPutWriter);
static assert(hasToString!(inout(H), char) == customPutWriterFormatSpec);
static assert(hasToString!(inout(I), char) == customPutWriterFormatSpec);
- static assert(hasToString!(inout(J), char) == hasSomeToString);
+ static assert(hasToString!(inout(J), char) == hasSomeToString
+ || hasToString!(inout(J), char) == constCharSinkFormatSpec); // depends on -preview=rvaluerefparam
static assert(hasToString!(inout(K), char) == constCharSinkFormatSpec);
- static assert(hasToString!(inout(L), char) == none);
+ static assert(hasToString!(inout(L), char) == customPutWriterFormatSpec);
static if (hasPreviewIn)
{
static assert(hasToString!(inout(M), char) == inCharSinkFormatSpec);
diff --git a/libphobos/src/std/format/read.d b/libphobos/src/std/format/read.d
index da9d0dc..e2f9b94 100644
--- a/libphobos/src/std/format/read.d
+++ b/libphobos/src/std/format/read.d
@@ -198,7 +198,8 @@ module std.format.read;
import std.format.spec : FormatSpec;
import std.format.internal.read;
-import std.traits : isSomeString;
+import std.meta : allSatisfy;
+import std.traits : isSomeString, isType;
/**
Reads an input range according to a format string and stores the read
@@ -300,7 +301,7 @@ uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, au
/// ditto
uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args)
-if (isSomeString!(typeof(fmt)))
+if (!isType!fmt && isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;
import std.meta : staticMap;
@@ -693,6 +694,116 @@ if (isSomeString!(typeof(fmt)))
}
/**
+Reads an input range according to a format string and returns a tuple of Args
+with the read values.
+
+Format specifiers with format character $(B 'd'), $(B 'u') and $(B
+'c') can take a $(B '*') parameter for skipping values.
+
+The second version of `formattedRead` takes the format string as
+template argument. In this case, it is checked for consistency at
+compile-time.
+
+Params:
+ Args = a variadic list of types of the arguments
+ */
+template formattedRead(Args...)
+if (Args.length && allSatisfy!(isType, Args))
+{
+ import std.typecons : Tuple;
+
+ /**
+ Params:
+ r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
+ where the formatted input is read from
+ fmt = a $(MREF_ALTTEXT format string, std,format)
+ Range = the type of the input range `r`
+ Char = the character type used for `fmt`
+
+ Returns:
+ A Tuple!Args with the elements filled.
+
+ Throws:
+ A $(REF_ALTTEXT FormatException, FormatException, std, format)
+ if reading did not succeed.
+ */
+ Tuple!Args formattedRead(Range, Char)(auto ref Range r, const(Char)[] fmt)
+ {
+ import core.lifetime : forward;
+ import std.format : enforceFmt;
+
+ Tuple!Args args;
+ const numArgsFilled = .formattedRead(forward!r, fmt, args.expand);
+ enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments");
+ return args;
+ }
+}
+
+///
+@safe pure unittest
+{
+ import std.exception : assertThrown;
+ import std.format : FormatException;
+ import std.typecons : tuple;
+
+ auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s");
+ assert(complete == tuple("hello", 34.5, 124));
+
+ // reading ends early
+ assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s"));
+}
+
+/// Skipping values
+@safe pure unittest
+{
+ import std.format : FormatException;
+ import std.typecons : tuple;
+
+ auto result = "orange: (12%) 15.25".formattedRead!(string, double)("%s: (%*d%%) %f");
+ assert(result == tuple("orange", 15.25));
+}
+
+/// ditto
+template formattedRead(alias fmt, Args...)
+if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args))
+{
+ import std.typecons : Flag, Tuple, Yes;
+ Tuple!Args formattedRead(Range)(auto ref Range r)
+ {
+ import core.lifetime : forward;
+ import std.format : enforceFmt;
+
+ Tuple!Args args;
+ const numArgsFilled = .formattedRead!fmt(forward!r, args.expand);
+ enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments");
+ return args;
+ }
+}
+
+/// The format string can be checked at compile-time
+@safe pure unittest
+{
+ import std.exception : assertThrown;
+ import std.format : FormatException;
+ import std.typecons : tuple;
+
+ auto expected = tuple("hello", 124, 34.5);
+ auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double);
+ assert(result == expected);
+
+ assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int));
+}
+
+/// Compile-time consistency check
+@safe pure unittest
+{
+ import std.format : FormatException;
+ import std.typecons : tuple;
+
+ static assert(!__traits(compiles, "orange: (12%) 15.25".formattedRead!("%s: (%*d%%) %f", string, double)));
+}
+
+/**
Reads a value from the given _input range and converts it according to a
format specifier.
diff --git a/libphobos/src/std/logger/core.d b/libphobos/src/std/logger/core.d
index cc938d4..1e879fd 100644
--- a/libphobos/src/std/logger/core.d
+++ b/libphobos/src/std/logger/core.d
@@ -1433,7 +1433,7 @@ logger by the user, the default logger's log level is LogLevel.info.
Example:
-------------
-sharedLog = new FileLogger(yourFile);
+sharedLog = new shared FileLogger(yourFile);
-------------
The example sets a new `FileLogger` as new `sharedLog`.
@@ -1450,7 +1450,7 @@ writing `sharedLog`.
The default `Logger` is thread-safe.
-------------
if (sharedLog !is myLogger)
- sharedLog = new myLogger;
+ sharedLog = new shared myLogger;
-------------
*/
@property shared(Logger) sharedLog() @safe
diff --git a/libphobos/src/std/logger/filelogger.d b/libphobos/src/std/logger/filelogger.d
index c662ca7..5ba167c 100644
--- a/libphobos/src/std/logger/filelogger.d
+++ b/libphobos/src/std/logger/filelogger.d
@@ -37,7 +37,7 @@ class FileLogger : Logger
auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes);
-------------
*/
- this(const string fn, const LogLevel lv = LogLevel.all) @safe
+ this(this This)(const string fn, const LogLevel lv = LogLevel.all)
{
this(fn, lv, CreateFolder.yes);
}
@@ -63,7 +63,7 @@ class FileLogger : Logger
auto l2 = new FileLogger(file, LogLevel.fatal);
-------------
*/
- this(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe
+ this(this This)(const string fn, const LogLevel lv, CreateFolder createFileNameFolder)
{
import std.file : exists, mkdirRecurse;
import std.path : dirName;
@@ -80,7 +80,8 @@ class FileLogger : Logger
" created in '", d,"' could not be created."));
}
- this.file_.open(this.filename, "a");
+ // Cast away `shared` when the constructor is inferred shared.
+ () @trusted { (cast() this.file_).open(this.filename, "a"); }();
}
/** A constructor for the `FileLogger` Logger that takes a reference to
@@ -270,3 +271,12 @@ class FileLogger : Logger
assert(tl !is null);
stdThreadLocalLog.logLevel = LogLevel.all;
}
+
+@safe unittest
+{
+ // we don't need to actually run the code, only make sure
+ // it compiles
+ static _() {
+ auto l = new shared FileLogger("");
+ }
+}
diff --git a/libphobos/src/std/logger/package.d b/libphobos/src/std/logger/package.d
index 14a4394..215ca20 100644
--- a/libphobos/src/std/logger/package.d
+++ b/libphobos/src/std/logger/package.d
@@ -64,7 +64,7 @@ using the property called `sharedLog`. This property is a reference to the
current default `Logger`. This reference can be used to assign a new
default `Logger`.
-------------
-sharedLog = new FileLogger("New_Default_Log_File.log");
+sharedLog = new shared FileLogger("New_Default_Log_File.log");
-------------
Additional `Logger` can be created by creating a new instance of the
diff --git a/libphobos/src/std/numeric.d b/libphobos/src/std/numeric.d
index 3fef8e4..9966b1c 100644
--- a/libphobos/src/std/numeric.d
+++ b/libphobos/src/std/numeric.d
@@ -223,7 +223,7 @@ private:
}
// Convert the current value to signed exponent, normalized form
- void toNormalized(T,U)(ref T sig, ref U exp)
+ void toNormalized(T,U)(ref T sig, ref U exp) const
{
sig = significand;
auto shift = (T.sizeof*8) - precision;
@@ -490,7 +490,7 @@ public:
}
/// Returns: real part
- @property CustomFloat re() { return this; }
+ @property CustomFloat re() const { return this; }
/// Returns: imaginary part
static @property CustomFloat im() { return CustomFloat(0.0f); }
@@ -546,7 +546,7 @@ public:
}
/// Fetches the stored value either as a `float`, `double` or `real`.
- @property F get(F)()
+ @property F get(F)() const
if (staticIndexOf!(immutable F, immutable float, immutable double, immutable real) >= 0)
{
import std.conv : text;
@@ -591,14 +591,14 @@ public:
// Define an opBinary `CustomFloat op CustomFloat` so that those below
// do not match equally, which is disallowed by the spec:
// https://dlang.org/spec/operatoroverloading.html#binary
- real opBinary(string op,T)(T b)
+ real opBinary(string op,T)(T b) const
if (__traits(compiles, mixin(`get!real`~op~`b.get!real`)))
{
return mixin(`get!real`~op~`b.get!real`);
}
/// ditto
- real opBinary(string op,T)(T b)
+ real opBinary(string op,T)(T b) const
if ( __traits(compiles, mixin(`get!real`~op~`b`)) &&
!__traits(compiles, mixin(`get!real`~op~`b.get!real`)))
{
@@ -606,7 +606,7 @@ public:
}
/// ditto
- real opBinaryRight(string op,T)(T a)
+ real opBinaryRight(string op,T)(T a) const
if ( __traits(compiles, mixin(`a`~op~`get!real`)) &&
!__traits(compiles, mixin(`get!real`~op~`b`)) &&
!__traits(compiles, mixin(`get!real`~op~`b.get!real`)))
@@ -615,7 +615,7 @@ public:
}
/// ditto
- int opCmp(T)(auto ref T b)
+ int opCmp(T)(auto ref T b) const
if (__traits(compiles, cast(real) b))
{
auto x = get!real;
@@ -949,6 +949,17 @@ public:
assertThrown!AssertError(a = float.infinity);
}
+@safe unittest
+{
+ const CustomFloat!16 x = CustomFloat!16(3);
+ assert(x.get!float == 3);
+ assert(x.re.get!float == 3);
+ assert(x + x == 6);
+ assert(x + 1 == 4);
+ assert(2 + x == 5);
+ assert(x < 4);
+}
+
private bool isCorrectCustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags) @safe pure nothrow @nogc
{
// Restrictions from bitfield
diff --git a/libphobos/src/std/process.d b/libphobos/src/std/process.d
index 4f593bd..2efbcaa 100644
--- a/libphobos/src/std/process.d
+++ b/libphobos/src/std/process.d
@@ -4631,11 +4631,12 @@ else version (Posix)
if (childpid == 0)
{
// Trusted because args and all entries are always zero-terminated
- (() @trusted =>
- core.sys.posix.unistd.execvp(args[0], &args[0]) ||
- perror(args[0]) // failed to execute
- )();
- return;
+ (() @trusted {
+ core.sys.posix.unistd.execvp(args[0], &args[0]);
+ perror(args[0]);
+ core.sys.posix.unistd._exit(1);
+ })();
+ assert(0, "Child failed to exec");
}
if (browser)
// Trusted because it's allocated via strdup above
diff --git a/libphobos/src/std/socket.d b/libphobos/src/std/socket.d
index 52fd33b..7fa9974 100644
--- a/libphobos/src/std/socket.d
+++ b/libphobos/src/std/socket.d
@@ -54,6 +54,12 @@ version (Windows)
enum socket_t : SOCKET { INVALID_SOCKET }
private const int _SOCKET_ERROR = SOCKET_ERROR;
+ /**
+ * On Windows, there is no `SO_REUSEPORT`.
+ * However, `SO_REUSEADDR` is equivalent to `SO_REUSEPORT` there.
+ * $(LINK https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse)
+ */
+ private enum SO_REUSEPORT = SO_REUSEADDR;
private int _lasterr() nothrow @nogc
{
@@ -2589,6 +2595,22 @@ enum SocketOption: int
DEBUG = SO_DEBUG, /// Record debugging information
BROADCAST = SO_BROADCAST, /// Allow transmission of broadcast messages
REUSEADDR = SO_REUSEADDR, /// Allow local reuse of address
+ /**
+ * Allow local reuse of port
+ *
+ * On Windows, this is equivalent to `SocketOption.REUSEADDR`.
+ * There is in fact no option named `REUSEPORT`.
+ * However, `SocketOption.REUSEADDR` matches the behavior of
+ * `SocketOption.REUSEPORT` on other platforms. Further details on this
+ * topic can be found here:
+ * $(LINK https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse)
+ *
+ * On Linux, this ensures fair distribution of incoming connections accross threads.
+ *
+ * See_Also:
+ * https://lwn.net/Articles/542629/
+ */
+ REUSEPORT = SO_REUSEPORT,
LINGER = SO_LINGER, /// Linger on close if unsent data is present
OOBINLINE = SO_OOBINLINE, /// Receive out-of-band data in band
SNDBUF = SO_SNDBUF, /// Send buffer size
diff --git a/libphobos/src/std/sumtype.d b/libphobos/src/std/sumtype.d
index 69c2a49..ad29428 100644
--- a/libphobos/src/std/sumtype.d
+++ b/libphobos/src/std/sumtype.d
@@ -1860,88 +1860,65 @@ private template Iota(size_t n)
assert(Iota!3 == AliasSeq!(0, 1, 2));
}
-/* The number that the dim-th argument's tag is multiplied by when
- * converting TagTuples to and from case indices ("caseIds").
- *
- * Named by analogy to the stride that the dim-th index into a
- * multidimensional static array is multiplied by to calculate the
- * offset of a specific element.
- */
-private size_t stride(size_t dim, lengths...)()
-{
- import core.checkedint : mulu;
-
- size_t result = 1;
- bool overflow = false;
-
- static foreach (i; 0 .. dim)
- {
- result = mulu(result, lengths[i], overflow);
- }
-
- /* The largest number matchImpl uses, numCases, is calculated with
- * stride!(SumTypes.length), so as long as this overflow check
- * passes, we don't need to check for overflow anywhere else.
- */
- assert(!overflow, "Integer overflow");
- return result;
-}
-
private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
{
auto ref matchImpl(SumTypes...)(auto ref SumTypes args)
if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
{
- alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes));
- alias TagTuple = .TagTuple!(SumTypes);
-
- /*
- * A list of arguments to be passed to a handler needed for the case
- * labeled with `caseId`.
- */
- template handlerArgs(size_t caseId)
+ // Single dispatch (fast path)
+ static if (args.length == 1)
{
- enum tags = TagTuple.fromCaseId(caseId);
- enum argsFrom(size_t i : tags.length) = "";
- enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~
- ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1);
- enum handlerArgs = argsFrom!0;
- }
+ /* When there's only one argument, the caseId is just that
+ * argument's tag, so there's no need for TagTuple.
+ */
+ enum handlerArgs(size_t caseId) =
+ "args[0].get!(SumTypes[0].Types[" ~ toCtString!caseId ~ "])()";
- /* An AliasSeq of the types of the member values in the argument list
- * returned by `handlerArgs!caseId`.
- *
- * Note that these are the actual (that is, qualified) types of the
- * member values, which may not be the same as the types listed in
- * the arguments' `.Types` properties.
- */
- template valueTypes(size_t caseId)
+ alias valueTypes(size_t caseId) =
+ typeof(args[0].get!(SumTypes[0].Types[caseId])());
+
+ enum numCases = SumTypes[0].Types.length;
+ }
+ // Multiple dispatch (slow path)
+ else
{
- enum tags = TagTuple.fromCaseId(caseId);
+ alias typeCounts = Map!(typeCount, SumTypes);
+ alias stride(size_t i) = .stride!(i, typeCounts);
+ alias TagTuple = .TagTuple!typeCounts;
+
+ alias handlerArgs(size_t caseId) = .handlerArgs!(caseId, typeCounts);
- template getType(size_t i)
+ /* An AliasSeq of the types of the member values in the argument list
+ * returned by `handlerArgs!caseId`.
+ *
+ * Note that these are the actual (that is, qualified) types of the
+ * member values, which may not be the same as the types listed in
+ * the arguments' `.Types` properties.
+ */
+ template valueTypes(size_t caseId)
{
- enum tid = tags[i];
- alias T = SumTypes[i].Types[tid];
- alias getType = typeof(args[i].get!T());
+ enum tags = TagTuple.fromCaseId(caseId);
+
+ template getType(size_t i)
+ {
+ enum tid = tags[i];
+ alias T = SumTypes[i].Types[tid];
+ alias getType = typeof(args[i].get!T());
+ }
+
+ alias valueTypes = Map!(getType, Iota!(tags.length));
}
- alias valueTypes = Map!(getType, Iota!(tags.length));
+ /* The total number of cases is
+ *
+ * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length
+ *
+ * Conveniently, this is equal to stride!(SumTypes.length), so we can
+ * use that function to compute it.
+ */
+ enum numCases = stride!(SumTypes.length);
}
- /* The total number of cases is
- *
- * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length
- *
- * Or, equivalently,
- *
- * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof
- *
- * Conveniently, this is equal to stride!(SumTypes.length), so we can
- * use that function to compute it.
- */
- enum numCases = stride!(SumTypes.length);
-
/* Guaranteed to never be a valid handler index, since
* handlers.length <= size_t.max.
*/
@@ -1998,7 +1975,12 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
mixin("alias ", handlerName!hid, " = handler;");
}
- immutable argsId = TagTuple(args).toCaseId;
+ // Single dispatch (fast path)
+ static if (args.length == 1)
+ immutable argsId = args[0].tag;
+ // Multiple dispatch (slow path)
+ else
+ immutable argsId = TagTuple(args).toCaseId;
final switch (argsId)
{
@@ -2029,10 +2011,11 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
}
}
+// Predicate for staticMap
private enum typeCount(SumType) = SumType.Types.length;
-/* A TagTuple represents a single possible set of tags that `args`
- * could have at runtime.
+/* A TagTuple represents a single possible set of tags that the arguments to
+ * `matchImpl` could have at runtime.
*
* Because D does not allow a struct to be the controlling expression
* of a switch statement, we cannot dispatch on the TagTuple directly.
@@ -2054,22 +2037,23 @@ private enum typeCount(SumType) = SumType.Types.length;
* When there is only one argument, the caseId is equal to that
* argument's tag.
*/
-private struct TagTuple(SumTypes...)
+private struct TagTuple(typeCounts...)
{
- size_t[SumTypes.length] tags;
+ size_t[typeCounts.length] tags;
alias tags this;
- alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes));
+ alias stride(size_t i) = .stride!(i, typeCounts);
invariant
{
static foreach (i; 0 .. tags.length)
{
- assert(tags[i] < SumTypes[i].Types.length, "Invalid tag");
+ assert(tags[i] < typeCounts[i], "Invalid tag");
}
}
- this(ref const(SumTypes) args)
+ this(SumTypes...)(ref const SumTypes args)
+ if (allSatisfy!(isSumType, SumTypes) && args.length == typeCounts.length)
{
static foreach (i; 0 .. tags.length)
{
@@ -2104,6 +2088,52 @@ private struct TagTuple(SumTypes...)
}
}
+/* The number that the dim-th argument's tag is multiplied by when
+ * converting TagTuples to and from case indices ("caseIds").
+ *
+ * Named by analogy to the stride that the dim-th index into a
+ * multidimensional static array is multiplied by to calculate the
+ * offset of a specific element.
+ */
+private size_t stride(size_t dim, lengths...)()
+{
+ import core.checkedint : mulu;
+
+ size_t result = 1;
+ bool overflow = false;
+
+ static foreach (i; 0 .. dim)
+ {
+ result = mulu(result, lengths[i], overflow);
+ }
+
+ /* The largest number matchImpl uses, numCases, is calculated with
+ * stride!(SumTypes.length), so as long as this overflow check
+ * passes, we don't need to check for overflow anywhere else.
+ */
+ assert(!overflow, "Integer overflow");
+ return result;
+}
+
+/* A list of arguments to be passed to a handler needed for the case
+ * labeled with `caseId`.
+ */
+private template handlerArgs(size_t caseId, typeCounts...)
+{
+ enum tags = TagTuple!typeCounts.fromCaseId(caseId);
+
+ alias handlerArgs = AliasSeq!();
+
+ static foreach (i; 0 .. tags.length)
+ {
+ handlerArgs = AliasSeq!(
+ handlerArgs,
+ "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~
+ ".Types[" ~ toCtString!(tags[i]) ~ "])(), "
+ );
+ }
+}
+
// Matching
@safe unittest
{
diff --git a/libphobos/src/std/traits.d b/libphobos/src/std/traits.d
index 69362c0..f230aa3 100644
--- a/libphobos/src/std/traits.d
+++ b/libphobos/src/std/traits.d
@@ -7251,16 +7251,21 @@ alias PointerTarget(T : T*) = T;
/**
* Detect whether type `T` is an aggregate type.
*/
-enum bool isAggregateType(T) = is(T == struct) || is(T == union) ||
- is(T == class) || is(T == interface);
+template isAggregateType(T)
+{
+ static if (is(T == enum))
+ enum isAggregateType = isAggregateType!(OriginalType!T);
+ else
+ enum isAggregateType = is(T == struct) || is(T == class) || is(T == interface) || is(T == union);
+}
///
@safe unittest
{
- class C;
- union U;
- struct S;
- interface I;
+ class C {}
+ union U {}
+ struct S {}
+ interface I {}
static assert( isAggregateType!C);
static assert( isAggregateType!U);
@@ -7271,6 +7276,16 @@ enum bool isAggregateType(T) = is(T == struct) || is(T == union) ||
static assert(!isAggregateType!(int[]));
static assert(!isAggregateType!(C[string]));
static assert(!isAggregateType!(void delegate(int)));
+
+ enum ES : S { a = S.init }
+ enum EC : C { a = C.init }
+ enum EI : I { a = I.init }
+ enum EU : U { a = U.init }
+
+ static assert( isAggregateType!ES);
+ static assert( isAggregateType!EC);
+ static assert( isAggregateType!EI);
+ static assert( isAggregateType!EU);
}
/**
@@ -9238,12 +9253,16 @@ enum isCopyable(S) = __traits(isCopyable, S);
* is the same as `T`. For pointer and slice types, it is `T` with the
* outer-most layer of qualifiers dropped.
*/
-package(std) template DeducedParameterType(T)
+package(std) alias DeducedParameterType(T) = DeducedParameterTypeImpl!T;
+/// ditto
+package(std) alias DeducedParameterType(alias T) = DeducedParameterTypeImpl!T;
+
+private template DeducedParameterTypeImpl(T)
{
static if (is(T == U*, U) || is(T == U[], U))
- alias DeducedParameterType = Unqual!T;
+ alias DeducedParameterTypeImpl = Unqual!T;
else
- alias DeducedParameterType = T;
+ alias DeducedParameterTypeImpl = T;
}
@safe unittest
@@ -9263,6 +9282,7 @@ package(std) template DeducedParameterType(T)
}
static assert(is(DeducedParameterType!NoCopy == NoCopy));
+ static assert(is(DeducedParameterType!(inout(NoCopy)) == inout(NoCopy)));
}
@safe unittest
diff --git a/libphobos/src/std/typecons.d b/libphobos/src/std/typecons.d
index c874c0f..bd462f5 100644
--- a/libphobos/src/std/typecons.d
+++ b/libphobos/src/std/typecons.d
@@ -3104,17 +3104,18 @@ private:
{
static if (useQualifierCast)
{
- this.data = cast() value;
+ static if (hasElaborateAssign!T)
+ {
+ import core.lifetime : copyEmplace;
+ copyEmplace(cast() value, this.data);
+ }
+ else
+ this.data = cast() value;
}
else
{
- // As we're escaping a copy of `value`, deliberately leak a copy:
- static union DontCallDestructor
- {
- T value;
- }
- DontCallDestructor copy = DontCallDestructor(value);
- this.data = *cast(Payload*) &copy;
+ import core.lifetime : copyEmplace;
+ copyEmplace(cast() value, cast() *cast(T*) &this.data);
}
}
@@ -3139,6 +3140,334 @@ package(std) Rebindable2!T rebindable2(T)(T value)
return Rebindable2!T(value);
}
+// Verify that the destructor is called properly if there is one.
+@system unittest
+{
+ {
+ bool destroyed;
+
+ struct S
+ {
+ int i;
+
+ this(int i) @safe
+ {
+ this.i = i;
+ }
+
+ ~this() @safe
+ {
+ destroyed = true;
+ }
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+
+ // Whether destruction has occurred here depends on whether the
+ // temporary gets moved or not, so we won't assume that it has or
+ // hasn't happened. What we care about here is that foo gets destroyed
+ // properly when it leaves the scope.
+ destroyed = false;
+ }
+ assert(destroyed);
+
+ {
+ auto foo = rebindable2(const S(42));
+ destroyed = false;
+ }
+ assert(destroyed);
+ }
+
+ // Test for double destruction with qualifer cast being used
+ {
+ static struct S
+ {
+ int i;
+ bool destroyed;
+
+ this(int i) @safe
+ {
+ this.i = i;
+ }
+
+ ~this() @safe
+ {
+ destroyed = true;
+ }
+
+ @safe invariant
+ {
+ assert(!destroyed);
+ }
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+ assert(typeof(foo).useQualifierCast);
+ assert(foo.data.i == 42);
+ assert(!foo.data.destroyed);
+ }
+ {
+ auto foo = rebindable2(S(42));
+ destroy(foo);
+ }
+ {
+ auto foo = rebindable2(const S(42));
+ assert(typeof(foo).useQualifierCast);
+ assert(foo.data.i == 42);
+ assert(!foo.data.destroyed);
+ }
+ {
+ auto foo = rebindable2(const S(42));
+ destroy(foo);
+ }
+ }
+
+ // Test for double destruction without qualifer cast being used
+ {
+ static struct S
+ {
+ int i;
+ bool destroyed;
+
+ this(int i) @safe
+ {
+ this.i = i;
+ }
+
+ ~this() @safe
+ {
+ destroyed = true;
+ }
+
+ @disable ref S opAssign()(auto ref S rhs);
+
+ @safe invariant
+ {
+ assert(!destroyed);
+ }
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+ assert(!typeof(foo).useQualifierCast);
+ assert((cast(S*)&(foo.data)).i == 42);
+ assert(!(cast(S*)&(foo.data)).destroyed);
+ }
+ {
+ auto foo = rebindable2(S(42));
+ destroy(foo);
+ }
+ }
+}
+
+// Verify that if there is an overloaded assignment operator, it's not assigned
+// to garbage.
+@safe unittest
+{
+ static struct S
+ {
+ int i;
+ bool destroyed;
+
+ this(int i) @safe
+ {
+ this.i = i;
+ }
+
+ ~this() @safe
+ {
+ destroyed = true;
+ }
+
+ ref opAssign()(auto ref S rhs)
+ {
+ assert(!this.destroyed);
+ this.i = rhs.i;
+ return this;
+ }
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+ foo = S(99);
+ assert(foo.data.i == 99);
+ }
+ {
+ auto foo = rebindable2(S(42));
+ foo = const S(99);
+ assert(foo.data.i == 99);
+ }
+}
+
+// Verify that postblit or copy constructor is called properly if there is one.
+@system unittest
+{
+ // postblit with type qualifier cast
+ {
+ static struct S
+ {
+ int i;
+ static bool copied;
+
+ this(this) @safe
+ {
+ copied = true;
+ }
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+
+ // Whether a copy has occurred here depends on whether the
+ // temporary gets moved or not, so we won't assume that it has or
+ // hasn't happened. What we care about here is that foo gets copied
+ // properly when we copy it below.
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ }
+ {
+ auto foo = rebindable2(const S(42));
+ assert(typeof(foo).useQualifierCast);
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ }
+ }
+
+ // copy constructor with type qualifier cast
+ {
+ static struct S
+ {
+ int i;
+ static bool copied;
+
+ this(ref inout S rhs) @safe inout
+ {
+ this.i = i;
+ copied = true;
+ }
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+ assert(typeof(foo).useQualifierCast);
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ }
+ {
+ auto foo = rebindable2(const S(42));
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ }
+ }
+
+ // FIXME https://issues.dlang.org/show_bug.cgi?id=24829
+
+ // Making this work requires either reworking how the !useQualiferCast
+ // version works so that the compiler can correctly generate postblit
+ // constructors and copy constructors as appropriate, or an explicit
+ // postblit or copy constructor needs to be added for such cases, which
+ // gets pretty complicated if we want to correctly add the same attributes
+ // that T's postblit or copy constructor has.
+
+ /+
+ // postblit without type qualifier cast
+ {
+ static struct S
+ {
+ int* ptr;
+ static bool copied;
+
+ this(int i)
+ {
+ ptr = new int(i);
+ }
+
+ this(this) @safe
+ {
+ if (ptr !is null)
+ ptr = new int(*ptr);
+ copied = true;
+ }
+
+ @disable ref S opAssign()(auto ref S rhs);
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+ assert(!typeof(foo).useQualifierCast);
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr);
+ assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr);
+ }
+ {
+ auto foo = rebindable2(const S(42));
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr);
+ assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr);
+ }
+ }
+
+ // copy constructor without type qualifier cast
+ {
+ static struct S
+ {
+ int* ptr;
+ static bool copied;
+
+ this(int i)
+ {
+ ptr = new int(i);
+ }
+
+ this(ref inout S rhs) @safe inout
+ {
+ if (rhs.ptr !is null)
+ ptr = new inout int(*rhs.ptr);
+ copied = true;
+ }
+
+ @disable ref S opAssign()(auto ref S rhs);
+ }
+
+ {
+ auto foo = rebindable2(S(42));
+ assert(!typeof(foo).useQualifierCast);
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr);
+ assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr);
+ }
+ {
+ auto foo = rebindable2(const S(42));
+ S.copied = false;
+
+ auto bar = foo;
+ assert(S.copied);
+ assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr);
+ assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr);
+ }
+ }
+ +/
+}
+
/**
Similar to `Rebindable!(T)` but strips all qualifiers from the reference as
opposed to just constness / immutability. Primary intended use case is with
diff --git a/libphobos/src/std/windows/syserror.d b/libphobos/src/std/windows/syserror.d
index 3d8c5e7..dadf0e8 100644
--- a/libphobos/src/std/windows/syserror.d
+++ b/libphobos/src/std/windows/syserror.d
@@ -69,7 +69,6 @@ import core.sys.windows.winbase, core.sys.windows.winnt;
import std.array : appender, Appender;
import std.conv : to, toTextRange, text;
import std.exception;
-import std.windows.charset;
string sysErrorString(
DWORD errCode,