diff options
Diffstat (limited to 'libphobos/src/std')
-rw-r--r-- | libphobos/src/std/algorithm/searching.d | 82 | ||||
-rw-r--r-- | libphobos/src/std/array.d | 59 | ||||
-rw-r--r-- | libphobos/src/std/bitmanip.d | 23 | ||||
-rw-r--r-- | libphobos/src/std/container/dlist.d | 25 | ||||
-rw-r--r-- | libphobos/src/std/conv.d | 11 | ||||
-rw-r--r-- | libphobos/src/std/digest/package.d | 342 | ||||
-rw-r--r-- | libphobos/src/std/format/internal/write.d | 33 | ||||
-rw-r--r-- | libphobos/src/std/format/read.d | 115 | ||||
-rw-r--r-- | libphobos/src/std/logger/core.d | 4 | ||||
-rw-r--r-- | libphobos/src/std/logger/filelogger.d | 16 | ||||
-rw-r--r-- | libphobos/src/std/logger/package.d | 2 | ||||
-rw-r--r-- | libphobos/src/std/numeric.d | 25 | ||||
-rw-r--r-- | libphobos/src/std/process.d | 11 | ||||
-rw-r--r-- | libphobos/src/std/socket.d | 22 | ||||
-rw-r--r-- | libphobos/src/std/sumtype.d | 182 | ||||
-rw-r--r-- | libphobos/src/std/traits.d | 38 | ||||
-rw-r--r-- | libphobos/src/std/typecons.d | 345 | ||||
-rw-r--r-- | libphobos/src/std/windows/syserror.d | 1 |
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*) © + 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, |