diff --git a/Makefile b/Makefile index 4fa51299e60..e363de610b0 100644 --- a/Makefile +++ b/Makefile @@ -173,6 +173,10 @@ ifdef NO_AUTODECODE override DFLAGS += -version=NoAutodecodeStrings endif +ifdef LINUX_LEGACY_EMULATE_GETRANDOM +override DFLAGS += -version=linux_legacy_emulate_getrandom +endif + UDFLAGS=-unittest -version=StdUnittest LINKDL:=$(if $(findstring $(OS),linux),-L-ldl,) @@ -276,7 +280,7 @@ EXTRA_MODULES_INTERNAL := $(addprefix std/, \ scopebuffer test/dummyrange test/range \ test/sumtype_example_overloads \ $(addprefix unicode_, comp decomp grapheme norm tables) \ - windows/advapi32 \ + windows/advapi32 windows/bcrypt \ ) \ typetuple \ ) diff --git a/changelog/add_lazycache_function.dd b/changelog/add_lazycache_function.dd deleted file mode 100644 index 7797ea7b223..00000000000 --- a/changelog/add_lazycache_function.dd +++ /dev/null @@ -1,15 +0,0 @@ -Add lazyCache to std.algorithm.iteration - -The new `lazyCache` function provides a lazily evaluated range caching mechanism. -Unlike `cache`, which eagerly evaluates range elements during construction, -`lazyCache` defers evaluation until elements are explicitly requested. - ---- -auto result = iota(-4, 5).map!(a => tuple(a, expensiveComputation(a)))().lazyCache(); -// No computations performed at this point - -auto firstElement = result.front; -// First element is now evaluated ---- - -See the $(REF lazyCache, std,algorithm,iteration) documentation for more details. \ No newline at end of file diff --git a/changelog/entropy_system.dd b/changelog/entropy_system.dd deleted file mode 100644 index 707e87e12eb..00000000000 --- a/changelog/entropy_system.dd +++ /dev/null @@ -1,32 +0,0 @@ -Add an internal multi-backend entropy system - -This Phobos release introduces an internal multi-backend system for the -retrieval of entropy (as in cryptographically-secure random numbers obtained -from a suitable random number generator provided by the operating system). - -The current implementation supports the `getrandom` syscall on Linux. - -On BSD systems `arc4random_buf` or `getentropy` are used — depending on -which is implemented by the OS and powered by a secure (non-RC4) algorithm. - -Additionally, reading entropy from the character devices `/dev/urandom` and -`/dev/random` is available on all POSIX targets. - -On Windows `BCryptGenRandom` (from the -$(I Cryptography API: Next Generation (“BCrypt”))) is provided as a backend. -`CryptGenRandom` from the legacy $(I CryptoAPI) is not supported for the time -being. - -Furthermore, this replaces the `getrandom` backwards compatibility shim -that had been added by v2.111.1 for Linux targets. -Instead backwards compatibility is now provided by a hunt strategy algorithm -that tries potentially available entropy sources one by one to find one that -is available on the running system. -Given that the character devices serve as a fallback option here, -`urandom` is favored over `random`. That is because modern kernel versions — -where `random` would exhibit the usually more preferable behavior of blocking -only until the entropy pool has been initialized — will also provide the -`getrandom` syscall in the first place. Performing the syscall, in turn, is -even better as it does not depend on the runtime environment exposing the -special devices in predefined locations, thus working also within chroot -environments. diff --git a/changelog/unicode-17.dd b/changelog/unicode-17.dd deleted file mode 100644 index 6630ee5eaf1..00000000000 --- a/changelog/unicode-17.dd +++ /dev/null @@ -1,16 +0,0 @@ -std.uni has been upgraded from Unicode 16.0.0 to 17.0.0 - -This Unicode update was released September 9, 2025, and adds new blocks with characters. -See: https://www.unicode.org/versions/Unicode17.0.0/ - -``` -import std; - -void main() -{ - const alphaCount = iota(0, dchar.max).filter!(std.uni.isAlpha).walkLength; - writeln(alphaCount); - // formerly: 142759 - // now: 147421 -} -``` diff --git a/changelog/uuidv7.dd b/changelog/uuidv7.dd deleted file mode 100644 index 3313203a293..00000000000 --- a/changelog/uuidv7.dd +++ /dev/null @@ -1,18 +0,0 @@ -Add uuid v7 support to `std.uuid` - -Add uuid v7 support to the UUID type located in `std.uuid`. -The first 48 bits of v7 stores the milliseconds since the unix epoch -(1970-01-01), additionally 74 bit are used to store random data. - -Example: ---- -SysTime st = DateTime(2025, 8, 19, 10, 38, 45); -UUID u = UUID(st); -SysTime o = u.v7Timestamp(); -assert(o == st); - -string s = u.toString(); -UUID u2 = UUID(s); -SysTime o2 = u2.v7Timestamp(); -assert(o2 == st); ---- diff --git a/changelog/write-text.dd b/changelog/write-text.dd deleted file mode 100644 index ba161c3a413..00000000000 --- a/changelog/write-text.dd +++ /dev/null @@ -1,20 +0,0 @@ -Add `writeText`, `writeWText`, and `writeDText` to `std.conv` - -These functions are variants of the existing `text`, `wtext`, and `dtext` -functions. Instead of returning a string, they write their output to an output -range. - -Like `text`, `writeText` can accept an -$(LINK2 $(ROOT_DIR)spec/istring.html, interpolated expression sequence) as an -argument. - -Example: - ---- -import std.conv : writeText; -import std.array : appender; - -auto output = appender!string(); -output.writeText(i"2 + 2 == $(2 + 2)"); -assert(output.data == "2 + 2 == 4"); ---- diff --git a/std/array.d b/std/array.d index e85eadb4090..dfcffdd8882 100644 --- a/std/array.d +++ b/std/array.d @@ -1083,14 +1083,6 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) } } -// from rt/lifetime.d -private extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow; - -// from rt/tracegc.d -version (D_ProfileGC) -private extern (C) void[] _d_newarrayUTrace(string file, size_t line, - string funcname, const scope TypeInfo ti, size_t length) pure nothrow; - private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow { static assert(I.length <= nDimensions!T, @@ -1141,18 +1133,19 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow which will inform the GC how to destroy the items in the block when it gets collected. - _d_newarrayU returns a void[], but with the length set according - to E.sizeof. + _d_newarrayU returns a E[], with the length set according to + to the size parameter. +/ + enum isShared = is (E == shared); version (D_ProfileGC) { // FIXME: file, line, function should be propagated from the // caller, not here. - *(cast(void[]*)&ret) = _d_newarrayUTrace(__FILE__, __LINE__, - __FUNCTION__, typeid(E[]), size); + ret = _d_newarrayUTrace!E(size, isShared, + __FILE__, __LINE__, __FUNCTION__); } else - *(cast(void[]*)&ret) = _d_newarrayU(typeid(E[]), size); + ret = _d_newarrayU!E(size, isShared); static if (minimallyInitialized && hasIndirections!E) // _d_newarrayU would have asserted if the multiplication below // had overflowed, so we don't have to check it again. @@ -3581,11 +3574,18 @@ See_Also: $(LREF appender) struct Appender(A) if (isDynamicArray!A) { - import std.format.spec : FormatSpec; + import core.memory : GC; private alias T = ElementEncodingType!A; - InPlaceAppender!A* impl; + private struct Data + { + size_t capacity; + Unqual!T[] arr; + bool tryExtendBlock = false; + } + + private Data* _data; /** * Constructs an `Appender` with a given array. Note that this does not copy the @@ -3593,17 +3593,27 @@ if (isDynamicArray!A) * it will be used by the appender. After initializing an appender on an array, * appending to the original array will reallocate. */ - this(A arr) @safe + this(A arr) @trusted { - impl = new InPlaceAppender!A(arr); - } + // initialize to a given array. + _data = new Data; + _data.arr = cast(Unqual!T[]) arr; //trusted - private void ensureInit() @safe - { - if (impl is null) + if (__ctfe) + return; + + // We want to use up as much of the block the array is in as possible. + // if we consume all the block that we can, then array appending is + // safe WRT built-in append, and we can use the entire block. + // We only do this for mutable types that can be extended. + static if (isMutable!T && is(typeof(arr.length = size_t.max))) { - impl = new InPlaceAppender!A; + immutable cap = arr.capacity; //trusted + // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed) + if (cap > arr.length) + arr.length = cap; } + _data.capacity = arr.length; } /** @@ -3616,10 +3626,14 @@ if (isDynamicArray!A) */ void reserve(size_t newCapacity) { - if (newCapacity != 0) + if (_data) + { + if (newCapacity > _data.capacity) + ensureAddable(newCapacity - _data.arr.length); + } + else { - ensureInit(); - impl.reserve(newCapacity); + ensureAddable(newCapacity); } } @@ -3630,11 +3644,11 @@ if (isDynamicArray!A) */ @property size_t capacity() const { - return impl ? impl.capacity : 0; + return _data ? _data.capacity : 0; } /// Returns: The number of elements appended. - @property size_t length() const => (impl is null) ? 0 : impl.length; + @property size_t length() const => _data ? _data.arr.length : 0; /** * Use opSlice() from now on. @@ -3642,219 +3656,29 @@ if (isDynamicArray!A) */ @property inout(T)[] data() inout { - return opSlice(); + return this[]; } /** * Returns: The managed array. */ - @property inout(T)[] opSlice() inout @safe - { - return impl ? impl.opSlice() : null; - } - - /** - * Appends `item` to the managed array. Performs encoding for - * `char` types if `A` is a differently typed `char` array. - * - * Params: - * item = the single item to append - */ - void put(U)(U item) - if (InPlaceAppender!A.canPutItem!U) - { - ensureInit(); - impl.put(item); - } - - // Const fixing hack. - void put(Range)(Range items) - if (InPlaceAppender!A.canPutConstRange!Range) - { - if (!items.empty) - { - ensureInit(); - impl.put(items); - } - } - - /** - * Appends an entire range to the managed array. Performs encoding for - * `char` elements if `A` is a differently typed `char` array. - * - * Params: - * items = the range of items to append - */ - void put(Range)(Range items) - if (InPlaceAppender!A.canPutRange!Range) - { - if (!items.empty) - { - ensureInit(); - impl.put(items); - } - } - - /** - * Appends to the managed array. - * - * See_Also: $(LREF Appender.put) - */ - alias opOpAssign(string op : "~") = put; - - - // only allow overwriting data on non-immutable and non-const data - static if (isMutable!T) - { - /** - * Clears the managed array. This allows the elements of the array to be reused - * for appending. - * - * Note: clear is disabled for immutable or const element types, due to the - * possibility that `Appender` might overwrite immutable data. - */ - void clear() @safe pure nothrow - { - if (impl) - { - impl.clear(); - } - } - - /** - * Shrinks the managed array to the given length. - * - * Throws: `Exception` if newlength is greater than the current array length. - * Note: shrinkTo is disabled for immutable or const element types. - */ - void shrinkTo(size_t newlength) @safe pure - { - import std.exception : enforce; - if (impl) - { - impl.shrinkTo(newlength); - } - else - { - enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength"); - } - } - } - - /** - * Gives a string in the form of `Appender!(A)(data)`. - * - * Params: - * w = A `char` accepting - * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives). - * fmt = A $(REF FormatSpec, std, format) which controls how the array - * is formatted. - * Returns: - * A `string` if `writer` is not set; `void` otherwise. - */ - string toString()() const - { - return InPlaceAppender!A.toStringImpl(Unqual!(typeof(this)).stringof, impl ? impl.data : null); - } - - /// ditto - template toString(Writer) - if (isOutputRange!(Writer, char)) - { - void toString(scope ref Writer w, scope const ref FormatSpec!char fmt) const - { - InPlaceAppender!A.toStringImpl(Unqual!(typeof(this)).stringof, impl ? impl.data : null, w, fmt); - } - } -} - -/// -@safe pure nothrow unittest -{ - auto app = appender!string(); - string b = "abcdefg"; - foreach (char c; b) - app.put(c); - assert(app[] == "abcdefg"); - - int[] a = [ 1, 2 ]; - auto app2 = appender(a); - app2.put(3); - app2.put([ 4, 5, 6 ]); - assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); -} - -package(std) struct InPlaceAppender(A) -if (isDynamicArray!A) -{ - import core.memory : GC; - import std.format.spec : FormatSpec; - - private alias T = ElementEncodingType!A; - - private - { - size_t _capacity; - Unqual!T[] arr; - bool tryExtendBlock = false; - } - - @disable this(ref InPlaceAppender); - - this(A arrIn) @trusted - { - arr = cast(Unqual!T[]) arrIn; //trusted - - if (__ctfe) - return; - - // We want to use up as much of the block the array is in as possible. - // if we consume all the block that we can, then array appending is - // safe WRT built-in append, and we can use the entire block. - // We only do this for mutable types that can be extended. - static if (isMutable!T && is(typeof(arrIn.length = size_t.max))) - { - immutable cap = arrIn.capacity; //trusted - // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed) - if (cap > arrIn.length) - arrIn.length = cap; - } - _capacity = arrIn.length; - } - - void reserve(size_t newCapacity) - { - if (newCapacity > _capacity) - ensureAddable(newCapacity - arr.length); - } - - @property size_t capacity() const - { - return _capacity; - } - - @property size_t length() const => arr.length; - - @property inout(T)[] data() inout - { - return this[]; - } - - inout(T)[] opSlice() inout @trusted + @property inout(T)[] opSlice() inout @trusted { /* @trusted operation: * casting Unqual!T[] to inout(T)[] */ - return cast(typeof(return)) arr; + return cast(typeof(return))(_data ? _data.arr : null); } // ensure we can add nelems elements, resizing as necessary private void ensureAddable(size_t nelems) { - immutable len = arr.length; + if (!_data) + _data = new Data; + immutable len = _data.arr.length; immutable reqlen = len + nelems; - if (_capacity >= reqlen) + if (_data.capacity >= reqlen) return; // need to increase capacity @@ -3862,17 +3686,17 @@ if (isDynamicArray!A) { static if (__traits(compiles, new Unqual!T[1])) { - arr.length = reqlen; + _data.arr.length = reqlen; } else { // avoid restriction of @disable this() - arr = arr[0 .. _capacity]; - foreach (i; _capacity .. reqlen) - arr ~= Unqual!T.init; + _data.arr = _data.arr[0 .. _data.capacity]; + foreach (i; _data.capacity .. reqlen) + _data.arr ~= Unqual!T.init; } - arr = arr[0 .. len]; - _capacity = reqlen; + _data.arr = _data.arr[0 .. len]; + _data.capacity = reqlen; } else { @@ -3880,11 +3704,11 @@ if (isDynamicArray!A) // Time to reallocate. // We need to almost duplicate what's in druntime, except we // have better access to the capacity field. - auto newlen = appenderNewCapacity!(T.sizeof)(_capacity, reqlen); + auto newlen = appenderNewCapacity!(T.sizeof)(_data.capacity, reqlen); // first, try extending the current block - if (tryExtendBlock) + if (_data.tryExtendBlock) { - immutable u = (() @trusted => GC.extend(arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); + immutable u = (() @trusted => GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); if (u) { // extend worked, update the capacity @@ -3893,10 +3717,11 @@ if (isDynamicArray!A) // at large unused blocks. static if (hasIndirections!T) { - immutable addedSize = u - (_capacity * T.sizeof); - () @trusted { memset(arr.ptr + _capacity, 0, addedSize); }(); + immutable addedSize = u - (_data.capacity * T.sizeof); + () @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }(); } - _capacity = u / T.sizeof; + + _data.capacity = u / T.sizeof; return; } } @@ -3910,11 +3735,11 @@ if (isDynamicArray!A) ~ "available pointer range"); auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))(); - _capacity = bi.size / T.sizeof; - import core.stdc.string : memcpy; + _data.capacity = bi.size / T.sizeof; if (len) - () @trusted { memcpy(bi.base, arr.ptr, len * T.sizeof); }(); - arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. 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 @@ -3925,7 +3750,7 @@ if (isDynamicArray!A) memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof); }(); - tryExtendBlock = true; + _data.tryExtendBlock = true; // leave the old data, for safety reasons } } @@ -3941,13 +3766,13 @@ if (isDynamicArray!A) enum bool canPutConstRange = isInputRange!(Unqual!Range) && !isInputRange!Range && - is(typeof(InPlaceAppender.init.put(Range.init.front))); + is(typeof(Appender.init.put(Range.init.front))); } private template canPutRange(Range) { enum bool canPutRange = isInputRange!Range && - is(typeof(InPlaceAppender.init.put(Range.init.front))); + is(typeof(Appender.init.put(Range.init.front))); } /** @@ -3976,13 +3801,13 @@ if (isDynamicArray!A) import core.lifetime : emplace; ensureAddable(1); - immutable len = arr.length; + immutable len = _data.arr.length; - auto bigData = (() @trusted => arr.ptr[0 .. len + 1])(); + auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])(); auto itemUnqual = (() @trusted => & cast() item)(); emplace(&bigData[len], *itemUnqual); //We do this at the end, in case of exceptions - arr = bigData; + _data.arr = bigData; } } @@ -4026,16 +3851,16 @@ if (isDynamicArray!A) auto bigDataFun(size_t extra) { ensureAddable(extra); - return (() @trusted => arr.ptr[0 .. arr.length + extra])(); + return (() @trusted => _data.arr.ptr[0 .. _data.arr.length + extra])(); } auto bigData = bigDataFun(items.length); - immutable len = arr.length; + immutable len = _data.arr.length; immutable newlen = bigData.length; alias UT = Unqual!T; - static if (is(typeof(arr[] = items[])) && + static if (is(typeof(_data.arr[] = items[])) && !hasElaborateAssign!UT && isAssignable!(UT, ElementEncodingType!Range)) { bigData[len .. newlen] = items[]; @@ -4051,7 +3876,7 @@ if (isDynamicArray!A) } //We do this at the end, in case of exceptions - arr = bigData; + _data.arr = bigData; } else static if (isSomeChar!T && isSomeChar!(ElementType!Range) && !is(immutable T == immutable ElementType!Range)) @@ -4094,7 +3919,10 @@ if (isDynamicArray!A) */ void clear() @trusted pure nothrow { - arr = arr.ptr[0 .. 0]; + if (_data) + { + _data.arr = _data.arr.ptr[0 .. 0]; + } } /** @@ -4106,8 +3934,13 @@ if (isDynamicArray!A) void shrinkTo(size_t newlength) @trusted pure { import std.exception : enforce; - enforce(newlength <= arr.length, "Attempting to shrink Appender with newlength > length"); - arr = arr.ptr[0 .. newlength]; + if (_data) + { + enforce(newlength <= _data.arr.length, "Attempting to shrink Appender with newlength > length"); + _data.arr = _data.arr.ptr[0 .. newlength]; + } + else + enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength"); } } @@ -4122,18 +3955,13 @@ if (isDynamicArray!A) * Returns: * A `string` if `writer` is not set; `void` otherwise. */ - auto toString() const - { - return toStringImpl(Unqual!(typeof(this)).stringof, data); - } - - static auto toStringImpl(string typeName, const T[] arr) + string toString()() const { import std.format.spec : singleSpec; - InPlaceAppender!string app; + auto app = appender!string(); auto spec = singleSpec("%s"); - immutable len = arr.length; + immutable len = _data ? _data.arr.length : 0; // different reserve lengths because each element in a // non-string-like array uses two extra characters for `, `. static if (isSomeString!A) @@ -4146,25 +3974,25 @@ if (isDynamicArray!A) // length, as it assumes each element is only one char app.reserve((len * 3) + 25); } - toStringImpl(typeName, arr, app, spec); + toString(app, spec); return app.data; } - void toString(Writer)(scope ref Writer w, scope const ref FormatSpec!char fmt) const - if (isOutputRange!(Writer, char)) - { - toStringImpl(Unqual!(typeof(this)).stringof, data, w, fmt); - } + import std.format.spec : FormatSpec; - static void toStringImpl(Writer)(string typeName, const T[] data, scope ref Writer w, - scope const ref FormatSpec!char fmt) + /// ditto + template toString(Writer) + if (isOutputRange!(Writer, char)) { - import std.format.write : formatValue; - import std.range.primitives : put; - put(w, typeName); - put(w, '('); - formatValue(w, data, fmt); - put(w, ')'); + void toString(ref Writer w, scope const ref FormatSpec!char fmt) const + { + import std.format.write : formatValue; + import std.range.primitives : put; + put(w, Unqual!(typeof(this)).stringof); + put(w, '('); + formatValue(w, data, fmt); + put(w, ')'); + } } } @@ -4207,16 +4035,6 @@ if (isDynamicArray!A) assert(app3[] == "Appender!(int[])(0001, 0002, 0003)"); } -@safe pure unittest -{ - auto app = appender!(char[])(); - app ~= "hello"; - app.clear; - // not a promise, just nothing else exercises capacity - // and this is the expected sort of behaviour - assert(app.capacity >= 5); -} - // https://issues.dlang.org/show_bug.cgi?id=17251 @safe pure nothrow unittest { @@ -4347,6 +4165,25 @@ if (isDynamicArray!A) writeln("WARNING: test of Appender zeroing did not occur"); } +// https://github.com/dlang/phobos/issues/10747 +@system unittest +{ + static struct A10747 + { + this(ref A10747 rhs) { } + } + + static class R10747 + { + A10747 front() { return A10747.init; } + void popFront() { } + bool empty = true; + } + + auto a = new R10747(); + a.array(); +} + //Calculates an efficient growth scheme based on the old capacity //of data, and the minimum requested capacity. //arg curLen: The current length @@ -4479,24 +4316,6 @@ unittest assert(app2.capacity >= 5); } -/++ - Convenience function that returns a $(LREF InPlaceAppender) instance, - optionally initialized with `array`. - +/ -package(std) InPlaceAppender!A inPlaceAppender(A)() -if (isDynamicArray!A) -{ - return InPlaceAppender!A(null); -} -/// ditto -package(std) InPlaceAppender!(E[]) inPlaceAppender(A : E[], E)(auto ref A array) -{ - static assert(!isStaticArray!A || __traits(isRef, array), - "Cannot create InPlaceAppender from an rvalue static array"); - - return InPlaceAppender!(E[])(array); -} - /++ Convenience function that returns an $(LREF Appender) instance, optionally initialized with `array`. diff --git a/std/container/rbtree.d b/std/container/rbtree.d index b5c8fa3352c..5b3964f659f 100644 --- a/std/container/rbtree.d +++ b/std/container/rbtree.d @@ -88,6 +88,14 @@ struct RBNode(V) private Node _right; private Node _parent; + private this(Node left, Node right, Node parent, V value) + { + this._left = left; + this._right = right; + this._parent = parent; + this.value = value; + } + /** * The value held by this node */ @@ -747,7 +755,7 @@ private struct RBRange(N) * inserted after all existing duplicate elements. */ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) -if (is(typeof((ref const T a) => binaryFun!less(a, a)))) +if (isSuitablePredicate!(less, T)) { import std.meta : allSatisfy; import std.range : Take; @@ -1995,7 +2003,9 @@ assert(equal(rbt[], [5])); } import std.range.primitives : isInputRange, ElementType; -import std.traits : isArray, isSomeString; +import std.traits : isArray, isSomeString, lvalueOf; + +private enum isSuitablePredicate(alias less, T) = is(typeof(binaryFun!less(lvalueOf!(const T), lvalueOf!(const T)))); /++ Convenience function for creating a `RedBlackTree!E` from a list of @@ -2020,14 +2030,14 @@ auto redBlackTree(bool allowDuplicates, E)(E[] elems...) /++ Ditto +/ auto redBlackTree(alias less, E)(E[] elems...) -if (is(typeof(binaryFun!less(E.init, E.init)))) +if (isSuitablePredicate!(less, E)) { return new RedBlackTree!(E, less)(elems); } /++ Ditto +/ auto redBlackTree(alias less, bool allowDuplicates, E)(E[] elems...) -if (is(typeof(binaryFun!less(E.init, E.init)))) +if (isSuitablePredicate!(less, E)) { //We shouldn't need to instantiate less here, but for some reason, //dmd can't handle it if we don't (even though the template which @@ -2051,7 +2061,7 @@ if (isInputRange!Stuff && !isArray!(Stuff)) /++ Ditto +/ auto redBlackTree(alias less, Stuff)(Stuff range) -if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init))) +if (isSuitablePredicate!(less, ElementType!Stuff) && isInputRange!Stuff && !isArray!(Stuff)) { return new RedBlackTree!(ElementType!Stuff, less)(range); @@ -2059,7 +2069,7 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init /++ Ditto +/ auto redBlackTree(alias less, bool allowDuplicates, Stuff)(Stuff range) -if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init))) +if (isSuitablePredicate!(less, ElementType!Stuff) && isInputRange!Stuff && !isArray!(Stuff)) { //We shouldn't need to instantiate less here, but for some reason, @@ -2276,3 +2286,17 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init cast(void) new RedBlackTree!(S, (ref const S a, ref const S b) => a.value > b.value); } + +// struct with move constructor +@safe unittest +{ + static struct S { + int i; + this(S s) { this.i = s.i; } + this(int i) { this.i = i; } + int opCmp(ref const S other) const @safe nothrow { return i - other.i; } + } + + auto tree = new RedBlackTree!(S, "a.i < b.i", false); + tree.insert(S(0)); +} diff --git a/std/experimental/allocator/common.d b/std/experimental/allocator/common.d index 245157258c1..b06fb627d71 100644 --- a/std/experimental/allocator/common.d +++ b/std/experimental/allocator/common.d @@ -63,8 +63,7 @@ unittest class C2 { char c; } static assert(stateSize!C2 == 4 * size_t.sizeof); static class C3 { char c; } - // Uncomment test after dmd issue closed https://github.com/dlang/dmd/issues/21065 - //static assert(stateSize!C3 == 3 * size_t.sizeof); + static assert(stateSize!C3 == 2 * size_t.sizeof + char.sizeof); } /** diff --git a/std/file.d b/std/file.d index a5b53270f95..0d4959b226f 100644 --- a/std/file.d +++ b/std/file.d @@ -969,6 +969,19 @@ if (isConvertibleToString!RF || isConvertibleToString!RT) assert(t2.readText == "2"); } + +@safe unittest +{ + import std.file; + import std.exception : assertThrown; + + string f = null; + + // Check if FileException is thrown for invalid rename + assertThrown!FileException(rename("", f)); + assertThrown!FileException(rename(f, "")); +} + private void renameImpl(scope const(char)[] f, scope const(char)[] t, scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted { @@ -983,10 +996,10 @@ private void renameImpl(scope const(char)[] f, scope const(char)[] t, import std.conv : to, text; if (!f) - f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]); + f = fromz ? to!(typeof(f))(fromz[0 .. wcslen(fromz)]) : "(null)"; if (!t) - t = to!(typeof(t))(toz[0 .. wcslen(toz)]); + t = toz ? to!(typeof(t))(toz[0 .. wcslen(toz)]) : "(null)"; enforce(false, new FileException( diff --git a/std/format/write.d b/std/format/write.d index d704c14f5fa..68a96d48625 100644 --- a/std/format/write.d +++ b/std/format/write.d @@ -534,6 +534,8 @@ uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] // Are we already done with formats? Then just dump each parameter in turn uint currentArg = 0; + bool lastWasConsumeAll; + while (spec.writeUpToNextSpec(w)) { if (currentArg == Args.length && !spec.indexStart) @@ -649,7 +651,10 @@ uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] } default: if (spec.indexEnd == spec.indexEnd.max) + { + lastWasConsumeAll = true; break; + } else if (spec.indexEnd == spec.indexStart) throw new FormatException( text("Positional specifier %", spec.indexStart, '$', spec.spec, @@ -660,7 +665,8 @@ uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] " index exceeds ", Args.length)); } } - return currentArg; + + return lastWasConsumeAll ? Args.length : currentArg; } /// @@ -1212,7 +1218,8 @@ if (isSomeString!(typeof(fmt))) import std.array : appender; auto w = appender!(char[])(); - formattedWrite(w, "%1:$d", 1, 2, 3); + uint count = formattedWrite(w, "%1:$d", 1, 2, 3); + assert(count == 3); assert(w.data == "123"); } diff --git a/std/internal/math/errorfunction.d b/std/internal/math/errorfunction.d index 8894ffb92ea..bb132ce8dea 100644 --- a/std/internal/math/errorfunction.d +++ b/std/internal/math/errorfunction.d @@ -984,6 +984,9 @@ Journal of Statistical Software 11, (July 2004). */ real normalDistributionImpl(real a) { + if (a is -real.infinity) return 0.0L; + if (a is real.infinity) return 1.0L; + real x = a * SQRT1_2; real z = fabs(x); @@ -1005,6 +1008,8 @@ real normalDistributionImpl(real a) { assert(fabs(normalDistributionImpl(1L) - (0.841344746068543L)) < 0.0000000000000005L); assert(isIdentical(normalDistributionImpl(NaN(0x325)), NaN(0x325))); +assert(normalDistributionImpl(-real.infinity) == 0.0L); +assert(normalDistributionImpl(real.infinity) == 1.0L); } /* diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index b1645d0c65e..1b05ebd48a2 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -168,12 +168,19 @@ real gammaStirling(real x) } /* - * Helper function: Incomplete gamma function computed by Temme's expansion. + * Regularized incomplete gamma function computed by Temme's expansion. If + * `compl` is true, it computes the complement of the regularized incomplete + * gamma function instead. + * + * For a derivation of the algorithm, see A "Set of Algorithms for the + * Incomplete Gamma Functions", N. M. Temme, Probability in the Engineering and + * Informational Sciences, Volume 8, Issue 2, April 1994, pp. 291-307, DOI: + * https://doi.org/10.1017/S0269964800003417. * * This is a port of igamma_temme_large from Boost. * */ -real igammaTemmeLarge(real a, real x) +real igammaTemmeLarge(real a, real x, bool compl) { static immutable real[][13] coef = [ [ -0.333333333333333333333L, 0.0833333333333333333333L, @@ -250,13 +257,12 @@ real igammaTemmeLarge(real a, real x) // avoid nans when one of the arguments is inf: if (x == real.infinity && a != real.infinity) - return 0; - + return compl ? 0.0L : 1.0L; if (x != real.infinity && a == real.infinity) - return 1; + return compl ? 1.0L : 0.0L; real sigma = (x - a) / a; - real phi = sigma - log(sigma + 1); + real phi = sigma - log1p(sigma); real y = a * phi; real z = sqrt(2 * phi); @@ -267,14 +273,40 @@ real igammaTemmeLarge(real a, real x) foreach (i; 0 .. coef.length) workspace[i] = poly(z, coef[i]); - real result = poly(1 / a, workspace); - result *= exp(-y) / sqrt(2 * PI * a); - if (x < a) - result = -result; + // Rₐ(𝜂) = [exp(-a𝜂²/2)/√(2𝜋a)]Sₐ(𝜂) + const r = exp(-y)/sqrt(2.0L*PI*a) * poly(1.0L/a, workspace); + + if (compl) + { + // Q(a,x) = erfc(+𝜂√(a/2))/2 + Rₐ(𝜂) + return erfc(+sgn(sigma)*sqrt(y))/2.0L + r; + } + else + { + // P(a,x) = erfc(-𝜂√(a/2))/2 - Rₐ(𝜂) + return erfc(-sgn(sigma)*sqrt(y))/2.0L - r; + } +} - result += erfc(sqrt(y)) / 2; +@safe unittest +{ + // Values were generated using scipy, which restricts values to double precision. + assert(feqrel(igammaTemmeLarge(25.0, 25.0, true), 0.47339_84685_56349_37L) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(25.0, 26.0, true), 0.39592_65699_99828_5L) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(26.0, 25.0, true), 0.55292_14200_24414_8L) >= double.mant_dig); + + assert(feqrel(igammaTemmeLarge(25.0, 25.0, false), 0.52660_15314_43650_6L) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(26.0, 25.0, false), 0.44707_85799_75585_2L) >= double.mant_dig); + + assert( + feqrel(igammaTemmeLarge(30.0L,31.0L,true), 1.0L-igammaTemmeLarge(30.0L,31.0L,false)) + >= real.mant_dig - 1); - return result; + assert(feqrel(igammaTemmeLarge(1e9, 1.1e9, false), 1.0) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(1.1e9, 1e9, false), 0.0) >= double.mant_dig); + + assert( + feqrel(igammaTemmeLarge(1e6, 1e6 + 1.0, true), 0.49946_80772_57932_46L) >= double.mant_dig); } } // private @@ -692,35 +724,34 @@ private pragma(inline, true) real betaLarge(in real x, in real y) */ real beta(in real x, in real y) { + // When one of the input parameters is NaN, return the NaN with the larger + // payload. This mimics the behavior of the + operator. + if (isNaN(x) || isNaN(y)) return cmp(abs(x), abs(y)) >= 0 ? x : y; + real res; + // Take advantage of the symmetry B(x,y) = B(y,x) + // smaller ≤ larger + const larger = cmp(x, y) >= 0 ? x : y; + const smaller = cmp(x, y) >= 0 ? y : x; + const sum = larger + smaller; + // the main algorithm - if (x > MAXGAMMA || y > MAXGAMMA || x + y > MAXGAMMA) + if (larger > MAXGAMMA || sum > MAXGAMMA) { - res = betaLarge(x, y); + res = betaLarge(smaller, larger); } else { - res = gamma(x) * gamma(y) / gamma(x+y); + res = gamma(smaller) * gamma(larger) / gamma(sum); // There are several regions near the asymptotes and inflection lines // gamma cannot be computed but logGamma can. - if (!isFinite(res)) res = betaLarge(x,y); + if (!isFinite(res)) res = betaLarge(smaller, larger); } if (!isNaN(res)) return res; - // For valid NaN results, always return the response from the main algorithm - // in order to preserve signaling NaNs. - - if (isNaN(x) || isNaN(y)) return res; - - // Take advantage of the symmetry B(x,y) = B(y,x) - // smaller ≤ larger - const larger = cmp(x, y) >= 0 ? x : y; - const smaller = cmp(x, y) >= 0 ? y : x; - const sum = larger + smaller; - // in a quadrant of the (smaller,larger) cartesian plane const inQ1 = cmp(smaller, +0.0L) >= 0; const inQ2 = !inQ1 && cmp(larger, +0.0L) >= 0; @@ -749,28 +780,35 @@ real beta(in real x, in real y) if (inQ1) { - // 4) On the larger axis and larger is finite, B = +∞ - // 5) On the larger axis, and larger is +∞, B = nan + // 4) At origin, B = +∞ + if (nextToOrigin) return +real.infinity; + + // 5) On the larger axis and larger is finite, B = +∞ + // 6) On the larger axis, and larger is +∞, B = nan if (nextToSmallAxis) return larger < +real.infinity ? +real.infinity : res; - // 6) Not on the larger axis, and the larger is +∞, B = +0 + // 7) Not on the larger axis, and the larger is +∞, B = +0 if (!nextToSmallAxis && larger == +real.infinity) return +0.; + + // not on larger axis, but near origin, case 4, or larger is very large, + // case 7 + if (!nextToSmallAxis) return larger < 1 ? +real.infinity : +0.; } if (inQ2) { - // 7) Next to the origin, B = nan - // 8) Next to the larger axis, but not the origin, B = -∞ + // 8) Next to the origin, B = nan + // 9) Next to the larger axis, but not the origin, B = -∞ if (nextToSmallAxis) return nextToOrigin ? res : -real.infinity; - // 9) Larger is +∞, B = ∞ * sgn(Γ(smaller)) + // 10) Larger is +∞, B = ∞ * sgn(Γ(smaller)) if (larger == +real.infinity) return copysign(real.infinity, sgnGamma(smaller)); - // 10) next to smaller axis, but not on an asymptote or at the origin, + // 11) next to smaller axis, but not on an asymptote or at the origin, // B = +∞. if (nextToLargeAxis && !onSmallAsymptote && !nextToOrigin) return +real.infinity; - // larger very large, case 9 + // larger very large, case 10 // larger so large that ln|Γ(larger)| and ln|Γ(sum)| are too large to // represent as reals. Thus they each are approximated as ∞, and the // main algorithm resolves to NaN instead of ±∞. @@ -779,10 +817,10 @@ real beta(in real x, in real y) if (inQ3) { - // 11) next to the smaller axis, but not on an asymptote, B = -∞. + // 12) next to the smaller axis, but not on an asymptote, B = -∞. if (nextToLargeAxis && !onSmallAsymptote) return -real.infinity; - // near origin, case 11 + // near origin, case 12 // -larger and -sum are so small that ln|Γ(larger)| and ln|Γ(sum)| are // too large to be represented as reals. Thus they each are approximated // as ∞, and the main algorithm resolves to NaN instead of -∞. @@ -795,14 +833,20 @@ real beta(in real x, in real y) @safe unittest { + // Test NaN payload propagation + assert(isIdentical(beta(NaN(0x1), 7), NaN(0x1))); assert(isIdentical(beta(2, NaN(0xABC)), NaN(0xABC))); + assert(isIdentical(beta(NaN(0x1), NaN(0x2)), NaN(0x2))); // Test symmetry + assert(beta(1, 2) is beta(2, 1)); // Test first quadrant + assert(beta(+0., +0.) == +real.infinity); assert(beta(+0., 1) == +real.infinity); - assert(beta(nextUp(+0.0L), nextUp(+0.0L) > 0), "B(εₓ,ε𞁟) > 0"); + assert(isClose(beta(nextUp(+0.0L), nextUp(+0.0L)), real.infinity), "B(εₓ,ε𞁟) ≲ +∞"); assert(!isNaN(beta(nextUp(+0.0L), 1)), "B(ε,y), y > 0 should exist"); + assert(isClose(beta(nextUp(+0.0L), nextDown(real.infinity)), +0.0L), "B(ε,y) ≳ 0, y large"); assert(beta(1, +real.infinity) is +0.0L, "lim{y→+∞} B(x,y) = 0⁺, x > 0"); assert(beta(1, 1) > 0); assert(beta(0.6*MAXGAMMA, 0.5*MAXGAMMA) > 0); @@ -1817,6 +1861,9 @@ do if ( (x > 1.0L) && (x > a ) ) return 1.0L - gammaIncompleteCompl(a,x); + if (a > 25.0L && abs(x-a) < 0.2L*a) + return igammaTemmeLarge(a, x, false); + real ax = a * log(x) - x - logGamma(a); /+ if ( ax < MINLOGL ) return 0; // underflow @@ -1872,7 +1919,7 @@ do // log(x)-x = NaN when x = real.infinity const real MAXLOGL = 1.1356523406294143949492E4L; if (x > MAXLOGL) - return igammaTemmeLarge(a, x); + return igammaTemmeLarge(a, x, true); real ax = a * log(x) - x - logGamma(a); //const real MINLOGL = -1.1355137111933024058873E4L; @@ -2110,6 +2157,9 @@ assert(gammaIncomplete(1, 0)==0); assert(gammaIncompleteCompl(1, 0)==1); assert(gammaIncomplete(4545, real.infinity)==1); +// Value was generated using scipy, which restricts values to double precision. +assert(feqrel(gammaIncomplete(1e20, 1e20), 0.50000_00000_13298) >= double.mant_dig); + // Values from Excel's (1-GammaDist(x, alpha, 1, TRUE)) assert(fabs(1.0L-gammaIncompleteCompl(0.5L, 2) - 0.954499729507309L) < 0.00000005L); @@ -2329,12 +2379,14 @@ real logmdigammaInverse(real y) immutable maxY = logmdigamma(real.min_normal); assert(maxY > 0 && maxY <= real.max); + if (isNaN(y)) return y; + if (y >= maxY) { //lim x->0 (log(x)-digamma(x))*x == 1 return 1 / y; } - if (y < 0) + if (signbit(y) == 1) // y is negative { return real.nan; } @@ -2343,13 +2395,10 @@ real logmdigammaInverse(real y) //6.3.18 return 0.5 / y; } - if (y > 0) - { - // x/2 <= logmdigamma(1 / x) <= x, x > 0 - // calls logmdigamma ~6 times - return 1 / findRoot((real x) => logmdigamma(1 / x) - y, y, 2*y); - } - return y; //NaN + + // x/2 <= logmdigamma(1 / x) <= x, x > 0 + // calls logmdigamma ~6 times + return 1 / findRoot((real x) => logmdigamma(1 / x) - y, y, 2*y); } @safe unittest @@ -2372,4 +2421,6 @@ real logmdigammaInverse(real y) assert(isClose(logmdigammaInverse(logmdigamma(1)), 1, 1e-15L)); assert(isClose(logmdigammaInverse(logmdigamma(real.min_normal)), real.min_normal, 1e-15L)); assert(isClose(logmdigammaInverse(logmdigamma(real.max/2)), real.max/2, 1e-15L)); + assert(logmdigammaInverse(NaN(0x1)) is NaN(0x1)); + assert(isNaN(logmdigammaInverse(-0.))); } diff --git a/std/internal/windows/bcrypt.d b/std/internal/windows/bcrypt.d new file mode 100644 index 00000000000..239dcd52e66 --- /dev/null +++ b/std/internal/windows/bcrypt.d @@ -0,0 +1,65 @@ +module std.internal.windows.bcrypt; + +version (Windows): + +import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG; +import core.sys.windows.windef : HMODULE, PUCHAR, ULONG; +import core.sys.windows.ntdef : NT_SUCCESS; + +pragma(lib, "Bcrypt.lib"); + +package(std) bool bcryptGenRandom(T)(out T result) @trusted +{ + loadBcrypt(); + + const gotRandom = ptrBCryptGenRandom( + null, + cast(PUCHAR) &result, + ULONG(T.sizeof), + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + + return NT_SUCCESS(gotRandom); +} + +private +{ + HMODULE hBcrypt = null; + typeof(BCryptGenRandom)* ptrBCryptGenRandom; +} + +private void loadBcrypt() @nogc nothrow +{ + import core.sys.windows.winbase : GetProcAddress, LoadLibraryA; + + if (!hBcrypt) + { + hBcrypt = LoadLibraryA("Bcrypt.dll"); + if (!hBcrypt) + assert(false, `LoadLibraryA("Bcrypt.dll") failed.`); // `@nogc` + + ptrBCryptGenRandom = cast(typeof(ptrBCryptGenRandom)) GetProcAddress(hBcrypt , "BCryptGenRandom"); + if (!ptrBCryptGenRandom) + assert(false, `GetProcAddress(hBcrypt , "BCryptGenRandom") failed.`); // `@nogc` + } +} + +// Will free `Bcrypt.dll`. +private void freeBcrypt() @nogc nothrow +{ + import core.sys.windows.winbase : FreeLibrary; + + if (hBcrypt) + { + if (!FreeLibrary(hBcrypt)) + assert(false, `FreeLibrary("Bcrypt.dll") failed.`); // `@nogc` + + hBcrypt = null; + ptrBCryptGenRandom = null; + } +} + +static ~this() +{ + freeBcrypt(); +} diff --git a/std/mathspecial.d b/std/mathspecial.d index 529d09e5b0e..2623fbc942b 100644 --- a/std/mathspecial.d +++ b/std/mathspecial.d @@ -187,21 +187,6 @@ pragma(inline, true) real beta(real x, real y) @safe unittest { assert(beta(1, 2) == 0.5); - assert(isIdentical(beta(NaN(0xABC), 4), NaN(0xABC))); - assert(beta(3, 4) == beta(4, 3)); - assert(isNaN(beta(-real.infinity, +0.))); - assert(isNaN(beta(-1, 2))); - assert(beta(-0.5, 0.5) is -0.0L); - assert(beta(-1.5, 0.5) is +0.0L); - assert(beta(+0., +0.) == +real.infinity); - assert(isNaN(beta(+0., +real.infinity))); - assert(beta(1, +real.infinity) is +0.0L); - assert(isNaN(beta(-0., +0.))); - assert(beta(-0., nextUp(+0.0L)) == -real.infinity); - assert(beta(-0.5, +real.infinity) == -real.infinity); - assert(beta(nextDown(-1.0L), real.infinity) == real.infinity); - assert(beta(nextDown(-0.0L), +0.) == +real.infinity); - assert(beta(-0.5, -0.) == -real.infinity); } /** Digamma function, $(PSI)(x) diff --git a/std/signals.d b/std/signals.d index 97004d52ddd..14b082fc20d 100644 --- a/std/signals.d +++ b/std/signals.d @@ -66,11 +66,6 @@ import core.exception : onOutOfMemoryError; import core.stdc.stdlib : calloc, realloc, free; import std.stdio; -// Special function for internal use only. -// Use of this is where the slot had better be a delegate -// to an object or an interface that is part of an object. -extern (C) Object _d_toObject(void* p); - // Used in place of Object.notifyRegister and Object.notifyUnRegister. alias DisposeEvt = void delegate(Object); extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt ); diff --git a/std/socket.d b/std/socket.d index 8f29c78f59f..1c04e67dbd7 100644 --- a/std/socket.d +++ b/std/socket.d @@ -3560,7 +3560,7 @@ public: if (checkError) checkError.setMinCapacity(n); } - int result = .select(n, fr, fw, fe, &timeout.ctimeval); + int result = .select(n, fr, fw, fe, timeout !is null ? &timeout.ctimeval : null); version (Windows) { diff --git a/std/uni/package.d b/std/uni/package.d index 34d15e034ba..d689f08e538 100644 --- a/std/uni/package.d +++ b/std/uni/package.d @@ -1785,6 +1785,7 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; { debug { + assert(accessIsSafe); arr[] = cast(typeof(T.init[0]))(0xdead_beef); } arr = null; @@ -1795,6 +1796,18 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; { arr = null; } + + // This is unfortunately necessary to "fake pure". It will only ever be called + // in the destructor for a GC-allocated CowArray, which is the only place where + // this might return false. Current code expects this to be pure, so we can't + // break that. But before this change, the code would access the referenced + // array inside a GC finalizer, which is invalid. + pragma(mangle, "gc_inFinalizer") private static extern(C) bool pureInGCFinalizer() @safe pure nothrow; + + static @property bool accessIsSafe() @safe nothrow pure + { + return __ctfe || !pureInGCFinalizer; + } } // ditto @@ -1892,6 +1905,8 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; pureFree(arr.ptr); arr = null; } + + enum accessIsSafe = true; } //build hack @@ -3234,6 +3249,10 @@ struct CowArray(SP=GcPolicy) ~this() { + if (!SP.accessIsSafe) + // detach from the array, we can no longer access it. + data = null; + if (!empty) { immutable cnt = refCount; diff --git a/std/variant.d b/std/variant.d index 484e60f536d..74b6065162d 100644 --- a/std/variant.d +++ b/std/variant.d @@ -1275,7 +1275,7 @@ public: auto arr = get!(A[]); foreach (ref e; arr) { - if (dg(e)) return 1; + if (auto r = dg(e)) return r; } } else static if (is(A == VariantN)) @@ -1287,7 +1287,7 @@ public: // Variant when in fact they are only changing tmp. auto tmp = this[i]; debug scope(exit) assert(tmp == this[i]); - if (dg(tmp)) return 1; + if (auto r = dg(tmp)) return r; } } else