diff options
author | Iain Buclaw <ibuclaw@gdcproject.org> | 2025-01-08 21:02:56 +0100 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gdcproject.org> | 2025-01-12 21:53:50 +0100 |
commit | 0dd21bce3afe9b0081d1d5bf4b53f9b43980c1c4 (patch) | |
tree | 4326f2031f50a7a326d79c25589ff50d00e36e77 /libphobos/src/std | |
parent | a236f70617213343f3075ee43e8d9f5882dca400 (diff) | |
download | gcc-0dd21bce3afe9b0081d1d5bf4b53f9b43980c1c4.zip gcc-0dd21bce3afe9b0081d1d5bf4b53f9b43980c1c4.tar.gz gcc-0dd21bce3afe9b0081d1d5bf4b53f9b43980c1c4.tar.bz2 |
d: Merge upstream dmd, druntime c57da0cf59, phobos ad8ee5587
D front-end changes:
- Import latest fixes from dmd v2.110.0-beta.1.
- The `align' attribute now allows to specify `default'
explicitly.
- Add primary expression of the form `__rvalue(expression)'
which causes `expression' to be treated as an rvalue, even if
it is an lvalue.
- Shortened method syntax can now be used in constructors.
D runtime changes:
- Import latest fixes from druntime v2.110.0-beta.1.
Phobos changes:
- Import latest fixes from phobos v2.110.0-beta.1.
gcc/d/ChangeLog:
* dmd/MERGE: Merge upstream dmd c57da0cf59.
* d-codegen.cc (can_elide_copy_p): New.
(d_build_call): Use it.
* d-lang.cc (d_post_options): Update for new front-end interface.
libphobos/ChangeLog:
* libdruntime/MERGE: Merge upstream druntime c57da0cf59.
* src/MERGE: Merge upstream phobos ad8ee5587.
* testsuite/libphobos.init_fini/custom_gc.d: Adjust test.
gcc/testsuite/ChangeLog:
* gdc.dg/copy1.d: New test.
Diffstat (limited to 'libphobos/src/std')
-rw-r--r-- | libphobos/src/std/algorithm/searching.d | 101 | ||||
-rw-r--r-- | libphobos/src/std/array.d | 58 | ||||
-rw-r--r-- | libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d | 621 | ||||
-rw-r--r-- | libphobos/src/std/format/package.d | 4 | ||||
-rw-r--r-- | libphobos/src/std/functional.d | 294 | ||||
-rw-r--r-- | libphobos/src/std/logger/filelogger.d | 2 | ||||
-rw-r--r-- | libphobos/src/std/logger/package.d | 4 | ||||
-rw-r--r-- | libphobos/src/std/math/traits.d | 52 | ||||
-rw-r--r-- | libphobos/src/std/path.d | 2 | ||||
-rw-r--r-- | libphobos/src/std/process.d | 6 | ||||
-rw-r--r-- | libphobos/src/std/stdio.d | 47 |
11 files changed, 965 insertions, 226 deletions
diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d index b7119d2..e0d2435 100644 --- a/libphobos/src/std/algorithm/searching.d +++ b/libphobos/src/std/algorithm/searching.d @@ -792,19 +792,18 @@ if (isInputRange!R && !isInfinite!R) `haystack` before reaching an element for which `startsWith!pred(haystack, needles)` is `true`. If `startsWith!pred(haystack, needles)` is not `true` for any element in - `haystack`, then `-1` is returned. If only `pred` is provided, - `pred(haystack)` is tested for each element. + `haystack`, then `-1` is returned. If more than one needle is provided, + `countUntil` will wrap the result in a tuple similar to + `Tuple!(ptrdiff_t, "steps", ptrdiff_t needle)` See_Also: $(REF indexOf, std,string) +/ -ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) +auto countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) if (isForwardRange!R && Rs.length > 0 && isForwardRange!(Rs[0]) == isInputRange!(Rs[0]) && allSatisfy!(canTestStartsWith!(pred, R), Rs)) { - typeof(return) result; - static if (needles.length == 1) { static if (hasLength!R) //Note: Narrow strings don't have length. @@ -814,14 +813,16 @@ if (isForwardRange!R auto len = haystack.length; auto r2 = find!pred(haystack, needles[0]); if (!r2.empty) - return cast(typeof(return)) (len - r2.length); + return ptrdiff_t(len - r2.length); } else { import std.range : dropOne; if (needles[0].empty) - return 0; + return ptrdiff_t(0); + + ptrdiff_t result; //Default case, slower route doing startsWith iteration for ( ; !haystack.empty ; ++result ) @@ -836,21 +837,39 @@ if (isForwardRange!R //haystack we pop in all paths, so we do that, and then save. haystack.popFront(); if (startsWith!pred(haystack.save, needles[0].save.dropOne())) - return result; + return result; } else - haystack.popFront(); + { + haystack.popFront(); + } } } + return ptrdiff_t(-1); } else { + static struct Result + { + ptrdiff_t steps, needle; // both -1 when nothing was found + alias steps this; // compatible with previous ptrdiff_t return type + ptrdiff_t opIndex(size_t idx) // faking a tuple + { + assert(idx < 2, "Type has only two members: pos and needle"); + return idx == 0 ? steps : needle; + } + } + Result result; + foreach (i, Ri; Rs) { static if (isForwardRange!Ri) { if (needles[i].empty) - return 0; + { + result.needle = i; + return result; + } } } Tuple!Rs t; @@ -861,7 +880,7 @@ if (isForwardRange!R t[i] = needles[i]; } } - for (; !haystack.empty ; ++result, haystack.popFront()) + for (; !haystack.empty ; ++result.steps, haystack.popFront()) { foreach (i, Ri; Rs) { @@ -870,18 +889,18 @@ if (isForwardRange!R t[i] = needles[i].save; } } - if (startsWith!pred(haystack.save, t.expand)) + if (auto needle = startsWith!pred(haystack.save, t.expand)) { + result.needle = needle - 1; return result; } } - } - // Because of https://issues.dlang.org/show_bug.cgi?id=8804 - // Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(false, R.stringof ~ " must not be an" - ~ " infinite range"); - else return -1; + // no match was found + result.needle = -1; + result.steps = -1; + return result; + } } /// ditto @@ -906,6 +925,16 @@ if (isInputRange!R && assert(countUntil([0, 7, 12, 22, 9], [12, 22]) == 2); assert(countUntil([0, 7, 12, 22, 9], 9) == 4); assert(countUntil!"a > b"([0, 7, 12, 22, 9], 20) == 3); + + // supports multiple needles + auto res = "...hello".countUntil("ha", "he"); + assert(res.steps == 3); + assert(res.needle == 1); + + // returns -1 if no needle was found + res = "hello".countUntil("ha", "hu"); + assert(res.steps == -1); + assert(res.needle == -1); } @safe unittest @@ -943,6 +972,42 @@ if (isInputRange!R && assert(countUntil("hello world", "world", 'l') == 2); } +@safe unittest +{ + auto res = "...hello".countUntil("hella", "hello"); + assert(res == 3); + assert(res.steps == 3); + assert(res.needle == 1); + // test tuple emulation + assert(res[0] == 3); + assert(res[1] == 1); + + // the first matching needle is chosen + res = "...hello".countUntil("hell", "hello"); + assert(res == 3); + assert(res.needle == 0); + + // no match + auto noMatch = "hello world".countUntil("ha", "hu"); + assert(noMatch == -1); + assert(noMatch.steps == -1); + assert(noMatch.needle == -1); + // test tuple emulation + assert(noMatch[0] == -1); + assert(noMatch[1] == -1); +} + +// test infinite ranges +@safe unittest +{ + import std.algorithm.iteration : joiner; + import std.range : iota, repeat; + import std.stdio; + assert(10.iota.repeat.joiner.countUntil(9) == 9); + assert(10.iota.repeat.joiner.countUntil(1, 2) == 1); + assert(10.iota.repeat.joiner.countUntil!(a => a >= 9) == 9); +} + /// ditto ptrdiff_t countUntil(alias pred, R)(R haystack) if (isInputRange!R && diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d index 3313dbb..fea7025 100644 --- a/libphobos/src/std/array.d +++ b/libphobos/src/std/array.d @@ -1297,6 +1297,64 @@ if (is(typeof(a.ptr < b.ptr) == bool)) static assert(test == "three"d); } +/// +@safe pure nothrow unittest +{ + import std.meta : AliasSeq; + + // can be used as an alternative implementation of overlap that returns + // `true` or `false` instead of a slice of the overlap + bool isSliceOf(T)(const scope T[] part, const scope T[] whole) + { + return part.overlap(whole) is part; + } + + auto x = [1, 2, 3, 4, 5]; + + assert(isSliceOf(x[3..$], x)); + assert(isSliceOf(x[], x)); + assert(!isSliceOf(x, x[3..$])); + assert(!isSliceOf([7, 8], x)); + assert(isSliceOf(null, x)); + + // null is a slice of itself + assert(isSliceOf(null, null)); + + foreach (T; AliasSeq!(int[], const(int)[], immutable(int)[], const int[], immutable int[])) + { + T a = [1, 2, 3, 4, 5]; + T b = a; + T c = a[1 .. $]; + T d = a[0 .. 1]; + T e = null; + + assert(isSliceOf(a, a)); + assert(isSliceOf(b, a)); + assert(isSliceOf(a, b)); + + assert(isSliceOf(c, a)); + assert(isSliceOf(c, b)); + assert(!isSliceOf(a, c)); + assert(!isSliceOf(b, c)); + + assert(isSliceOf(d, a)); + assert(isSliceOf(d, b)); + assert(!isSliceOf(a, d)); + assert(!isSliceOf(b, d)); + + assert(isSliceOf(e, a)); + assert(isSliceOf(e, b)); + assert(isSliceOf(e, c)); + assert(isSliceOf(e, d)); + + //verifies R-value compatibilty + assert(!isSliceOf(a[$ .. $], a)); + assert(isSliceOf(a[0 .. 0], a)); + assert(isSliceOf(a, a[0.. $])); + assert(isSliceOf(a[0 .. $], a)); + } +} + @safe pure nothrow unittest { static void test(L, R)(L l, R r) diff --git a/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d index ca83785..c6d5fca 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d @@ -40,6 +40,11 @@ longer used. It does so by destroying empty allocators. However, in order to avoid thrashing (excessive creation/destruction of allocators under certain use patterns), it keeps unused allocators for a while. +The shared version of `AllocatorList` is `SharedAllocatorList`, which has +identical semantics to its single-threaded version. Both `BookkeepingAllocator` +and `Allocator` provided by `factoryFunction` must be shared, in order to +ensure corectness. + Params: factoryFunction = A function or template function (including function literals). New allocators are created by calling `factoryFunction(n)` with strictly @@ -661,65 +666,285 @@ version (Posix) @system unittest assert(b1.length == 1024 * 10); } +/// Ditto +shared struct SharedAllocatorList(Factory, BookkeepingAllocator = GCAllocator) +{ + import std.typecons : Ternary; + import std.traits : hasMember; + import core.internal.spinlock : SpinLock; + +private: + // Forward all calls to 'impl' and protect them by the lock below + AllocatorList!(Factory, BookkeepingAllocator) impl; + SpinLock lock = SpinLock(SpinLock.Contention.brief); + + // This could be potentially removed in the future, + // should a successor to <https://github.com/dlang/druntime/pull/2156> + // or a solution to <https://github.com/dlang/dmd/issues/17128> get merged. + static ref T assumeUnshared(T)(ref shared T val) @trusted @nogc pure nothrow + { + return *cast(T*) &val; + } + + // Debug function used for testing + version (unittest) + auto allocators() + { + return impl.allocators; + } + +// Everything is inherited from the 'AllocatorList' implementation +public: + + /* + Note: This does not work well with rvalues because it copies them once more. + Probably not a problem here because all parameters are cheap. + <https://github.com/dlang/phobos/pull/6465/files#r189629862> + */ + + /** + The alignment offered. + */ + enum alignment = impl.alignment; + + /** + Allocate a block of size `s`. First tries to allocate from the existing + list of already-created allocators. If neither can satisfy the request, + creates a new allocator by calling `make(s)` and delegates the request + to it. However, if the allocation fresh off a newly created allocator + fails, subsequent calls to `allocate` will not cause more calls to $(D + make). + */ + static if (hasMember!(typeof(impl), "allocate")) + void[] allocate(size_t s) + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).allocate(s); + } + + /** + Allocate a block of size `s` with alignment `a`. First tries to allocate + from the existing list of already-created allocators. If neither can + satisfy the request, creates a new allocator by calling `make(s + a - 1)` + and delegates the request to it. However, if the allocation fresh off a + newly created allocator fails, subsequent calls to `alignedAllocate` + will not cause more calls to `make`. + */ + static if (hasMember!(typeof(impl), "alignedAllocate")) + void[] alignedAllocate(size_t s, uint a) + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).alignedAllocate(s, a); + } + + /** + Defined if `Allocator.deallocate` and `Allocator.owns` are defined. + */ + static if (hasMember!(typeof(impl), "deallocate")) + bool deallocate(void[] b) + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).deallocate(b); + } + + /** + Defined only if `Allocator` defines `owns`. Tries each allocator in + turn, in most-recently-used order. If the owner is found, it is moved to + the front of the list as a side effect under the assumption it will be used + soon. + + Returns: `Ternary.yes` if one allocator was found to return `Ternary.yes`, + `Ternary.no` if all component allocators returned `Ternary.no`, and + `Ternary.unknown` if no allocator returned `Ternary.yes` and at least one + returned `Ternary.unknown`. + */ + static if (hasMember!(typeof(impl), "owns")) + Ternary owns(void[] b) + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).owns(b); + } + + /** + Defined only if `Allocator.expand` is defined. Finds the owner of `b` + and calls `expand` for it. The owner is not brought to the head of the + list. + */ + static if (hasMember!(typeof(impl), "expand")) + bool expand(ref void[] b, size_t delta) + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).expand(b, delta); + } + + /** + Defined only if `Allocator.reallocate` is defined. Finds the owner of + `b` and calls `reallocate` for it. If that fails, calls the global + `reallocate`, which allocates a new block and moves memory. + */ + static if (hasMember!(typeof(impl), "reallocate")) + bool reallocate(ref void[] b, size_t s) + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).reallocate(b, s); + } + + /** + Defined only if `Allocator.owns` and `Allocator.deallocateAll` are + defined. + */ + static if (hasMember!(typeof(impl), "deallocateAll")) + bool deallocateAll() + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).deallocateAll(); + } + + /** + Returns `Ternary.yes` if no allocators are currently active, + `Ternary.no` otherwise. This methods never returns `Ternary.unknown`. + */ + static if (hasMember!(typeof(impl), "empty")) + Ternary empty() + { + lock.lock(); + scope(exit) lock.unlock(); + + return assumeUnshared(impl).empty(); + } +} + +/// Ditto +template SharedAllocatorList(alias factoryFunction, + BookkeepingAllocator = GCAllocator) +{ + alias A = typeof(factoryFunction(1)); + static assert( + // is a template function (including literals) + is(typeof({A function(size_t) @system x = factoryFunction!size_t;})) + || + // or a function (including literals) + is(typeof({A function(size_t) @system x = factoryFunction;})) + , + "Only function names and function literals that take size_t" + ~ " and return an allocator are accepted, not " + ~ typeof(factoryFunction).stringof + ); + static struct Factory + { + A opCall(size_t n) { return factoryFunction(n); } + } + alias SharedAllocatorList = .SharedAllocatorList!(Factory, BookkeepingAllocator); +} + @system unittest { - // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : Region, SharedRegion; + + static void testAlloc(Allocator)(ref Allocator a) + { + const b1 = a.allocate(1024 * 8192); + assert(b1 !is null); // still works due to overdimensioning + const b2 = a.allocate(1024 * 10); + assert(b2.length == 1024 * 10); + a.deallocateAll(); + } + + // Create an allocator based upon 4MB regions, fetched from the GC heap. AllocatorList!((n) => Region!GCAllocator(new ubyte[max(n, 1024 * 4096)]), - NullAllocator) a; - const b1 = a.allocate(1024 * 8192); - assert(b1 !is null); // still works due to overdimensioning - const b2 = a.allocate(1024 * 10); - assert(b2.length == 1024 * 10); - a.deallocateAll(); + NullAllocator) reg1; + + SharedAllocatorList!((n) => SharedRegion!GCAllocator(new ubyte[max(n, 1024 * 4096)]), + NullAllocator) reg2; + + testAlloc(reg1); + testAlloc(reg2); } @system unittest { - // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : BorrowedRegion; - AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; - auto b1 = a.alignedAllocate(1024 * 8192, 1024); - assert(b1 !is null); // still works due to overdimensioning - assert(b1.length == 1024 * 8192); - assert(b1.ptr.alignedAt(1024)); - assert(a.allocators.length == 1); + import std.experimental.allocator.building_blocks.region : BorrowedRegion, SharedBorrowedRegion; - b1 = a.alignedAllocate(0, 1024); - assert(b1.length == 0); - assert(a.allocators.length == 1); + static void testAlloc(Allocator)(ref Allocator a) + { + auto b1 = a.alignedAllocate(1024 * 8192, 1024); + assert(b1 !is null); // still works due to overdimensioning + assert(b1.length == 1024 * 8192); + assert(b1.ptr.alignedAt(1024)); + assert(a.allocators.length == 1); - b1 = a.allocate(1024 * 10); - assert(b1.length == 1024 * 10); + b1 = a.alignedAllocate(0, 1024); + assert(b1.length == 0); + assert(a.allocators.length == 1); - assert(a.reallocate(b1, 1024)); - assert(b1.length == 1024); + b1 = a.allocate(1024 * 10); + assert(b1.length == 1024 * 10); - a.deallocateAll(); + assert(a.reallocate(b1, 1024)); + assert(b1.length == 1024); + + a.deallocateAll(); + } + + // Create an allocator based upon 4MB regions, fetched from the GC heap. + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a1; + SharedAllocatorList!((n) => SharedBorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a2; + + testAlloc(a1); + testAlloc(a2); } @system unittest { import core.exception : AssertError; import std.exception : assertThrown; - - // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : BorrowedRegion; - AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; - auto b1 = a.alignedAllocate(0, 1); - assert(b1 is null); + import std.experimental.allocator.building_blocks.region : BorrowedRegion, SharedBorrowedRegion; + + static void testAlloc(Allocator)(ref Allocator a) + { + auto b1 = a.alignedAllocate(0, 1); + assert(b1 is null); + + b1 = a.alignedAllocate(1, 0); + assert(b1 is null); - b1 = a.alignedAllocate(1, 0); - assert(b1 is null); + b1 = a.alignedAllocate(0, 0); + assert(b1 is null); - b1 = a.alignedAllocate(0, 0); - assert(b1 is null); + assertThrown!AssertError(a.alignedAllocate(size_t.max, 1024)); - assertThrown!AssertError(a.alignedAllocate(size_t.max, 1024)); - a.deallocateAll(); + // FIXME: This special-casing might note be necessary. + // At the moment though, this call would take potentially forever + // for the `SharedAllocatorList` from below. + static if (!is(Allocator == shared)) + { + a.deallocateAll(); + } + } + + // Create an allocator based upon 4MB regions, fetched from the GC heap. + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a1; + SharedAllocatorList!((n) => SharedBorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a2; + + testAlloc(a1); + testAlloc(a2); } @system unittest @@ -728,52 +953,74 @@ version (Posix) @system unittest // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : BorrowedRegion; - AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; - auto b0 = a.alignedAllocate(1, 1024); - assert(b0.length == 1); - assert(b0.ptr.alignedAt(1024)); - assert(a.allocators.length == 1); + import std.experimental.allocator.building_blocks.region : BorrowedRegion, SharedBorrowedRegion; - auto b1 = a.alignedAllocate(1024 * 4096, 1024); - assert(b1.length == 1024 * 4096); - assert(b1.ptr.alignedAt(1024)); - assert(a.allocators.length == 2); - - auto b2 = a.alignedAllocate(1024, 128); - assert(b2.length == 1024); - assert(b2.ptr.alignedAt(128)); - assert(a.allocators.length == 2); + static void testAlloc(Allocator)(ref Allocator a) + { + auto b0 = a.alignedAllocate(1, 1024); + assert(b0.length == 1); + assert(b0.ptr.alignedAt(1024)); + assert(a.allocators.length == 1); + + auto b1 = a.alignedAllocate(1024 * 4096, 1024); + assert(b1.length == 1024 * 4096); + assert(b1.ptr.alignedAt(1024)); + assert(a.allocators.length == 2); + + auto b2 = a.alignedAllocate(1024, 128); + assert(b2.length == 1024); + assert(b2.ptr.alignedAt(128)); + assert(a.allocators.length == 2); + + auto b3 = a.allocate(1024); + assert(b3.length == 1024); + assert(a.allocators.length == 2); + + auto b4 = a.allocate(1024 * 4096); + assert(b4.length == 1024 * 4096); + assert(a.allocators.length == 3); + + static if (!is(Allocator == shared)) + { + assert(a.root.empty == Ternary.no); + assert(a.deallocate(b4)); + assert(a.root.empty == Ternary.yes); - auto b3 = a.allocate(1024); - assert(b3.length == 1024); - assert(a.allocators.length == 2); + assert(a.deallocate(b1)); + } - auto b4 = a.allocate(1024 * 4096); - assert(b4.length == 1024 * 4096); - assert(a.allocators.length == 3); + a.deallocateAll(); + } - assert(a.root.empty == Ternary.no); - assert(a.deallocate(b4)); - assert(a.root.empty == Ternary.yes); + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a1; + SharedAllocatorList!((n) => SharedBorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a2; - assert(a.deallocate(b1)); - a.deallocateAll(); + testAlloc(a1); + testAlloc(a2); } @system unittest { - // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : BorrowedRegion; - AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; - auto b1 = a.allocate(1024 * 8192); - assert(b1 !is null); // still works due to overdimensioning - b1 = a.allocate(1024 * 10); - assert(b1.length == 1024 * 10); - assert(a.reallocate(b1, 1024)); - assert(b1.length == 1024); - a.deallocateAll(); + import std.experimental.allocator.building_blocks.region : BorrowedRegion, SharedBorrowedRegion; + + static void testAlloc(Allocator)(ref Allocator a) + { + auto b1 = a.allocate(1024 * 8192); + assert(b1 !is null); // still works due to overdimensioning + b1 = a.allocate(1024 * 10); + assert(b1.length == 1024 * 10); + assert(a.reallocate(b1, 1024)); + assert(b1.length == 1024); + a.deallocateAll(); + } + + // Create an allocator based upon 4MB regions, fetched from the GC heap. + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a1; + SharedAllocatorList!((n) => SharedBorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a2; + + testAlloc(a1); + testAlloc(a2); } @system unittest @@ -798,24 +1045,33 @@ version (Posix) @system unittest @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : Region, SharedRegion; enum bs = GCAllocator.alignment; - AllocatorList!((n) => Region!GCAllocator(256 * bs)) a; - auto b1 = a.allocate(192 * bs); - assert(b1.length == 192 * bs); - assert(a.allocators.length == 1); - auto b2 = a.allocate(64 * bs); - assert(b2.length == 64 * bs); - assert(a.allocators.length == 1); - auto b3 = a.allocate(192 * bs); - assert(b3.length == 192 * bs); - assert(a.allocators.length == 2); - // Ensure deallocate inherits from parent allocators - () nothrow @nogc { a.deallocate(b1); }(); - b1 = a.allocate(64 * bs); - assert(b1.length == 64 * bs); - assert(a.allocators.length == 2); - a.deallocateAll(); + + static void testAlloc(Allocator)(ref Allocator a) + { + auto b1 = a.allocate(192 * bs); + assert(b1.length == 192 * bs); + assert(a.allocators.length == 1); + auto b2 = a.allocate(64 * bs); + assert(b2.length == 64 * bs); + assert(a.allocators.length == 1); + auto b3 = a.allocate(192 * bs); + assert(b3.length == 192 * bs); + assert(a.allocators.length == 2); + // Ensure deallocate inherits from parent allocators + () nothrow @nogc { a.deallocate(b1); }(); + b1 = a.allocate(64 * bs); + assert(b1.length == 64 * bs); + assert(a.allocators.length == 2); + a.deallocateAll(); + } + + AllocatorList!((n) => Region!GCAllocator(256 * bs)) a1; + SharedAllocatorList!((n) => SharedRegion!GCAllocator(256 * bs)) a2; + + testAlloc(a1); + testAlloc(a2); } @system unittest @@ -878,11 +1134,15 @@ version (Posix) @system unittest @system unittest { - import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.building_blocks.ascending_page_allocator : + AscendingPageAllocator, SharedAscendingPageAllocator; import std.experimental.allocator.mallocator : Mallocator; import std.algorithm.comparison : max; import std.typecons : Ternary; + enum pageSize = 4096; + enum numPages = 2; + static void testrw(void[] b) { ubyte* buf = cast(ubyte*) b.ptr; @@ -893,53 +1153,56 @@ version (Posix) @system unittest } } - enum numPages = 2; - AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a; + static void testAlloc(Allocator)(ref Allocator a) + { + void[] b1 = a.allocate(1); + assert(b1.length == 1); + b1 = a.allocate(2); + assert(b1.length == 2); + testrw(b1); - void[] b1 = a.allocate(1); - assert(b1.length == 1); - b1 = a.allocate(2); - assert(b1.length == 2); - testrw(b1); + void[] b2 = a.allocate((numPages + 1) * pageSize); + assert(b2.length == (numPages + 1) * pageSize); + testrw(b2); - void[] b2 = a.allocate((numPages + 1) * pageSize); - assert(b2.length == (numPages + 1) * pageSize); - testrw(b2); + void[] b3 = a.allocate(3); + assert(b3.length == 3); + testrw(b3); - void[] b3 = a.allocate(3); - assert(b3.length == 3); - testrw(b3); + void[] b4 = a.allocate(0); + assert(b4.length == 0); - void[] b4 = a.allocate(0); - assert(b4.length == 0); + assert(a.allocators.length == 3); + assert(a.owns(b1) == Ternary.yes); + assert(a.owns(b2) == Ternary.yes); + assert(a.owns(b3) == Ternary.yes); - assert(a.allocators.length == 3); - assert(a.owns(b1) == Ternary.yes); - assert(a.owns(b2) == Ternary.yes); - assert(a.owns(b3) == Ternary.yes); + assert(a.expand(b1, pageSize - b1.length)); + assert(b1.length == pageSize); + assert(!a.expand(b1, 1)); + assert(!a.expand(b2, 1)); - assert(a.expand(b1, pageSize - b1.length)); - assert(b1.length == pageSize); - assert(!a.expand(b1, 1)); - assert(!a.expand(b2, 1)); + testrw(b1); + testrw(b2); + testrw(b3); - testrw(b1); - testrw(b2); - testrw(b3); + assert(a.deallocate(b1)); + assert(a.deallocate(b2)); - assert(a.deallocate(b1)); - assert(a.deallocate(b2)); + const alignment = cast(uint) (70 * pageSize); + b3 = a.alignedAllocate(70 * pageSize, alignment); + assert(b3.length == 70 * pageSize); + assert(b3.ptr.alignedAt(alignment)); + testrw(b3); + assert(a.allocators.length == 4); + assert(a.deallocate(b3)); - const alignment = cast(uint) (70 * pageSize); - b3 = a.alignedAllocate(70 * pageSize, alignment); - assert(b3.length == 70 * pageSize); - assert(b3.ptr.alignedAt(alignment)); - testrw(b3); - assert(a.allocators.length == 4); - assert(a.deallocate(b3)); + assert(a.deallocateAll()); + } - assert(a.deallocateAll()); + AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a1; + SharedAllocatorList!((n) => SharedAscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a2; } @system unittest @@ -998,3 +1261,109 @@ version (Posix) @system unittest assert(a.deallocateAll()); } + +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.mallocator : Mallocator; + import std.algorithm.comparison : max; + import std.typecons : Ternary; + + enum pageSize = 4096; + + static void testrw(void[] b) + { + ubyte* buf = cast(ubyte*) b.ptr; + for (int i = 0; i < b.length; i += pageSize) + { + buf[i] = cast(ubyte) (i % 256); + assert(buf[i] == cast(ubyte) (i % 256)); + } + } + + enum numPages = 5; + AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a; + auto b = a.alignedAllocate(1, pageSize * 2); + assert(b.length == 1); + assert(a.expand(b, 4095)); + assert(b.ptr.alignedAt(2 * 4096)); + assert(b.length == 4096); + + b = a.allocate(4096); + assert(b.length == 4096); + assert(a.allocators.length == 1); + + assert(a.allocate(4096 * 5).length == 4096 * 5); + assert(a.allocators.length == 2); + + assert(a.deallocateAll()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : SharedRegion; + import core.thread : ThreadGroup; + import std.algorithm.comparison : max; + + enum numThreads = 10; + SharedAllocatorList!((n) => SharedRegion!(GCAllocator)(new ubyte[max(n, 1024)])) a; + + void fun() + { + void[] b1 = a.allocate(1024); + assert(b1.length == 1024); + + void[] b2 = a.alignedAllocate(1024, 1024); + assert(b2.length == 1024); + assert(b2.ptr.alignedAt(1024)); + + assert(a.deallocate(b1)); + assert(a.deallocate(b2)); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + assert(a.deallocateAll()); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.building_blocks.ascending_page_allocator : SharedAscendingPageAllocator; + import core.thread : ThreadGroup; + import std.algorithm.comparison : max; + + enum numThreads = 100; + enum pageSize = 4096; + enum numPages = 10; + SharedAllocatorList!((n) => SharedAscendingPageAllocator(max(n, pageSize * numPages)), Mallocator) a; + + void fun() + { + void[] b1 = a.allocate(512); + assert(b1.length == 512); + assert(a.expand(b1, 512)); + assert(b1.length == 1024); + + void[] b2 = a.alignedAllocate(1024, 4096); + assert(b2.length == 1024); + assert(b2.ptr.alignedAt(1024)); + + assert(a.deallocate(b1)); + assert(a.deallocate(b2)); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + assert(a.deallocateAll()); +} diff --git a/libphobos/src/std/format/package.d b/libphobos/src/std/format/package.d index e02c120..5dadce0 100644 --- a/libphobos/src/std/format/package.d +++ b/libphobos/src/std/format/package.d @@ -147,7 +147,7 @@ limited to a $(B '-') flag. $(H4 $(LNAME2 format-indicator, Format Indicator)) The $(I format indicator) can either be a single character or an -expression surrounded by $(B %\() and $(B %\)). It specifies the +expression surrounded by $(B '%$(LPAREN)') and $(B '%$(RPAREN)'). It specifies the basic manner in which a value will be formatted and is the minimum requirement to format a value. @@ -187,7 +187,7 @@ $(BOOKTABLE , The $(I compound indicator) can be used to describe compound types like arrays or structs in more detail. A compound type is enclosed -within $(B '%\(') and $(B '%\)'). The enclosed sub-format string is +within $(B '%$(LPAREN)') and $(B '%$(RPAREN)'). The enclosed sub-format string is applied to individual elements. The trailing portion of the sub-format string following the specifier for the element is interpreted as the delimiter, and is therefore omitted following the diff --git a/libphobos/src/std/functional.d b/libphobos/src/std/functional.d index 5022702..cf7e3ff 100644 --- a/libphobos/src/std/functional.d +++ b/libphobos/src/std/functional.d @@ -1289,6 +1289,15 @@ alias pipe(fun...) = compose!(Reverse!(fun)); assert(compose!(`a + 0.5`, `to!(int)(a) + 1`, foo)(1) == 2.5); } +private template getOverloads(alias fun) +{ + import std.meta : AliasSeq; + static if (__traits(compiles, __traits(getOverloads, __traits(parent, fun), __traits(identifier, fun), true))) + alias getOverloads = __traits(getOverloads, __traits(parent, fun), __traits(identifier, fun), true); + else + alias getOverloads = AliasSeq!fun; +} + /** * $(LINK2 https://en.wikipedia.org/wiki/Memoization, Memoizes) a function so as * to avoid repeated computation. The memoization structure is a hash table keyed by a @@ -1324,87 +1333,131 @@ Note: */ template memoize(alias fun) { - import std.traits : ReturnType; - // https://issues.dlang.org/show_bug.cgi?id=13580 - // alias Args = Parameters!fun; + import std.traits : Parameters; + import std.meta : anySatisfy; + + // Specific overloads: + alias overloads = getOverloads!fun; + static foreach (fn; overloads) + static if (is(Parameters!fn)) + alias memoize = impl!(Parameters!fn); + + enum isTemplate(alias a) = __traits(isTemplate, a); + static if (anySatisfy!(isTemplate, overloads)) + { + // Generic implementation + alias memoize = impl; + } - ReturnType!fun memoize(Parameters!fun args) + auto impl(Args...)(Args args) if (is(typeof(fun(args)))) { - alias Args = Parameters!fun; - import std.typecons : Tuple; + import std.typecons : Tuple, tuple; import std.traits : Unqual; - static Unqual!(ReturnType!fun)[Tuple!Args] memo; - auto t = Tuple!Args(args); - if (auto p = t in memo) - return *p; - auto r = fun(args); - memo[t] = r; - return r; + static if (args.length > 0) + { + static Unqual!(typeof(fun(args)))[Tuple!(typeof(args))] memo; + + auto t = Tuple!Args(args); + if (auto p = t in memo) + return *p; + auto r = fun(args); + memo[t] = r; + return r; + } + else + { + static typeof(fun(args)) result; + result = fun(args); + return result; + } } } /// ditto template memoize(alias fun, uint maxSize) { - import std.traits : ReturnType; - // https://issues.dlang.org/show_bug.cgi?id=13580 - // alias Args = Parameters!fun; - ReturnType!fun memoize(Parameters!fun args) + import std.traits : Parameters; + import std.meta : anySatisfy; + + // Specific overloads: + alias overloads = getOverloads!fun; + static foreach (fn; overloads) + static if (is(Parameters!fn)) + alias memoize = impl!(Parameters!fn); + + enum isTemplate(alias a) = __traits(isTemplate, a); + static if (anySatisfy!(isTemplate, overloads)) { - import std.meta : staticMap; - import std.traits : hasIndirections, Unqual; - import std.typecons : tuple; - static struct Value { staticMap!(Unqual, Parameters!fun) args; Unqual!(ReturnType!fun) res; } - static Value[] memo; - static size_t[] initialized; + // Generic implementation + alias memoize = impl; + } - if (!memo.length) + auto impl(Args...)(Args args) if (is(typeof(fun(args)))) + { + static if (args.length > 0) { - import core.memory : GC; + import std.meta : staticMap; + import std.traits : hasIndirections, Unqual; + import std.typecons : tuple; + alias returnType = typeof(fun(args)); + static struct Value { staticMap!(Unqual, Args) args; Unqual!returnType res; } + static Value[] memo; + static size_t[] initialized; - // Ensure no allocation overflows - static assert(maxSize < size_t.max / Value.sizeof); - static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); + if (!memo.length) + { + import core.memory : GC; - enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); - memo = (cast(Value*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; - enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); - initialized = (cast(size_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; - } + // Ensure no allocation overflows + static assert(maxSize < size_t.max / Value.sizeof); + static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); - import core.bitop : bt, bts; - import core.lifetime : emplace; + enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); + memo = (cast(Value*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; + enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); + initialized = (cast(size_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; + } - size_t hash; - foreach (ref arg; args) - hash = hashOf(arg, hash); - // cuckoo hashing - immutable idx1 = hash % maxSize; - if (!bt(initialized.ptr, idx1)) - { - emplace(&memo[idx1], args, fun(args)); - // only set to initialized after setting args and value - // https://issues.dlang.org/show_bug.cgi?id=14025 - bts(initialized.ptr, idx1); + import core.bitop : bt, bts; + import core.lifetime : emplace; + + size_t hash; + foreach (ref arg; args) + hash = hashOf(arg, hash); + // cuckoo hashing + immutable idx1 = hash % maxSize; + if (!bt(initialized.ptr, idx1)) + { + emplace(&memo[idx1], args, fun(args)); + // only set to initialized after setting args and value + // https://issues.dlang.org/show_bug.cgi?id=14025 + bts(initialized.ptr, idx1); + return memo[idx1].res; + } + else if (memo[idx1].args == args) + return memo[idx1].res; + // FNV prime + immutable idx2 = (hash * 16_777_619) % maxSize; + if (!bt(initialized.ptr, idx2)) + { + emplace(&memo[idx2], memo[idx1]); + bts(initialized.ptr, idx2); + } + else if (memo[idx2].args == args) + return memo[idx2].res; + else if (idx1 != idx2) + memo[idx2] = memo[idx1]; + + memo[idx1] = Value(args, fun(args)); return memo[idx1].res; } - else if (memo[idx1].args == args) - return memo[idx1].res; - // FNV prime - immutable idx2 = (hash * 16_777_619) % maxSize; - if (!bt(initialized.ptr, idx2)) + else { - emplace(&memo[idx2], memo[idx1]); - bts(initialized.ptr, idx2); + static typeof(fun(args)) result; + result = fun(args); + return result; } - else if (memo[idx2].args == args) - return memo[idx2].res; - else if (idx1 != idx2) - memo[idx2] = memo[idx1]; - - memo[idx1] = Value(args, fun(args)); - return memo[idx1].res; } } @@ -1464,6 +1517,37 @@ unittest assert(fact(10) == 3628800); } +// Issue 20099 +@system unittest // not @safe due to memoize +{ + int i = 3; + alias a = memoize!((n) => i + n); + alias b = memoize!((n) => i + n, 3); + + assert(a(3) == 6); + assert(b(3) == 6); +} + +@system unittest // not @safe due to memoize +{ + static Object objNum(int a) { return new Object(); } + assert(memoize!objNum(0) is memoize!objNum(0U)); + assert(memoize!(objNum, 3)(0) is memoize!(objNum, 3)(0U)); +} + +@system unittest // not @safe due to memoize +{ + struct S + { + static int fun() { return 0; } + static int fun(int i) { return 1; } + } + assert(memoize!(S.fun)() == 0); + assert(memoize!(S.fun)(3) == 1); + assert(memoize!(S.fun, 3)() == 0); + assert(memoize!(S.fun, 3)(3) == 1); +} + @system unittest // not @safe due to memoize { import core.math : sqrt; @@ -1626,6 +1710,19 @@ unittest }} } +// memoize should continue to work with functions that cannot be evaluated at compile time +@system unittest +{ + __gshared string[string] glob; + + static bool foo() + { + return (":-)" in glob) is null; + } + + assert(memoize!foo); +} + private struct DelegateFaker(F) { import std.typecons : FuncInfo, MemberFunctionGenerator; @@ -1717,13 +1814,45 @@ if (isCallable!(F)) { return fp; } + else static if (is(F Func == Func*) && is(Func == function)) + { + return function(ref F fp) @trusted + { + return buildDelegate(fp); + }(fp); + } else static if (is(typeof(&F.opCall) == delegate) || (is(typeof(&F.opCall) V : V*) && is(V == function))) { return toDelegate(&fp.opCall); } + else static if (is(typeof(&fp.opCall!()))) + { + return toDelegate(&fp.opCall!()); + } else { + static assert(false, "Unsupported type of callable, please open an issue."); + } +} + +/// +@safe unittest +{ + static int inc(ref uint num) { + num++; + return 8675309; + } + + uint myNum = 0; + auto incMyNumDel = toDelegate(&inc); + auto returnVal = incMyNumDel(myNum); + assert(myNum == 1); +} + +private template buildDelegate(F) +{ + auto buildDelegate(auto ref F fp) { alias DelType = typeof(&(new DelegateFaker!(F)).doIt); static struct DelegateFields { @@ -1753,21 +1882,22 @@ if (isCallable!(F)) } } -/// -@system unittest +@safe unittest { static int inc(ref uint num) { num++; return 8675309; } - uint myNum = 0; - auto incMyNumDel = toDelegate(&inc); - auto returnVal = incMyNumDel(myNum); - assert(myNum == 1); + uint myNum = 0x1337; + struct S1 { int opCall() { inc(myNum); return myNum; } } + static assert(!is(typeof(&s1.opCall) == delegate)); + S1 s1; + auto getvals1 = toDelegate(s1); + assert(getvals1() == 0x1338); } -@system unittest // not @safe due to toDelegate +@system unittest { static int inc(ref uint num) { num++; @@ -1852,6 +1982,36 @@ if (isCallable!(F)) } } + +@safe unittest +{ + static struct S1 { static void opCall()() { } } + static struct S2 { static T opCall(T = int)(T x) {return x; } } + + S1 i1; + const dg1 = toDelegate(i1); + dg1(); + + S2 i2; + assert(toDelegate(i2)(0xBED) == 0xBED); +} + +@safe unittest +{ + static void fun() @system pure nothrow @nogc + { + return; + } + + auto fn = &fun; + static assert( is(typeof(fn) == void function() @system pure nothrow @nogc)); + static assert(!is(typeof(fn) == void function() @safe pure nothrow @nogc)); + + auto dg = fn.toDelegate(); + static assert( is(typeof(dg) == void delegate() @system pure nothrow @nogc)); + static assert(!is(typeof(dg) == void delegate() @safe pure nothrow @nogc)); +} + /** * Passes the fields of a struct as arguments to a function. * diff --git a/libphobos/src/std/logger/filelogger.d b/libphobos/src/std/logger/filelogger.d index 5ba167c..754175c 100644 --- a/libphobos/src/std/logger/filelogger.d +++ b/libphobos/src/std/logger/filelogger.d @@ -276,7 +276,7 @@ class FileLogger : Logger { // we don't need to actually run the code, only make sure // it compiles - static _() { + static void _() { auto l = new shared FileLogger(""); } } diff --git a/libphobos/src/std/logger/package.d b/libphobos/src/std/logger/package.d index 215ca20..5a84749 100644 --- a/libphobos/src/std/logger/package.d +++ b/libphobos/src/std/logger/package.d @@ -17,7 +17,7 @@ The easiest way to create a log message is to write: import std.logger; void main() { - log("Hello World"); + info("Hello World"); } ------------- This will print a message to the `stderr` device. The message will contain @@ -59,7 +59,7 @@ $(UL $(LI `fatal`) ) The default `Logger` will by default log to `stderr` and has a default -`LogLevel` of `LogLevel.all`. The default Logger can be accessed by +`LogLevel` of `LogLevel.info`. The default Logger can be accessed by 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`. diff --git a/libphobos/src/std/math/traits.d b/libphobos/src/std/math/traits.d index 81ab1b7..8aba6a6 100644 --- a/libphobos/src/std/math/traits.d +++ b/libphobos/src/std/math/traits.d @@ -21,6 +21,7 @@ module std.math.traits; import std.traits : isFloatingPoint, isIntegral, isNumeric, isSigned; + /********************************* * Determines if $(D_PARAM x) is NaN. * Params: @@ -466,8 +467,27 @@ if (isFloatingPoint!(X)) */ bool isIdentical(real x, real y) @trusted pure nothrow @nogc { - import std.math.traits : floatTraits, RealFormat; - + if (__ctfe) + { + if (x !is y) return false; + if (x == x) return true; // If not NaN `is` implies identical representation. + static if (double.mant_dig != real.mant_dig) + { + // Works because we are in CTFE and there is no way in CTFE to set more + // bits of NaN payload than can fit in a double, and since 2.087 + // changed real.init to be non-signaling I *think* there is no way in + // CTFE for a real to be a signaling NaN unless real and double have + // the same representation so real's bits can be manipulated directly. + double d1 = x, d2 = y; + } + else + { + // Alias to avoid converting signaling to quiet. + alias d1 = x; + alias d2 = y; + } + return *cast(long*) &d1 == *cast(long*) &d2; + } // We're doing a bitwise comparison so the endianness is irrelevant. long* pxs = cast(long *)&x; long* pys = cast(long *)&y; @@ -491,20 +511,44 @@ bool isIdentical(real x, real y) @trusted pure nothrow @nogc assert(0, "isIdentical not implemented"); } } - /// @safe @nogc pure nothrow unittest { + // We're forcing the CTFE to run by assigning the result of the function to an enum + enum test1 = isIdentical(1.0,1.0); + enum test2 = isIdentical(real.nan,real.nan); + enum test3 = isIdentical(real.infinity, real.infinity); + enum test4 = isIdentical(real.infinity, real.infinity); + enum test5 = isIdentical(0.0, 0.0); + + assert(test1); + assert(test2); + assert(test3); + assert(test4); + assert(test5); + + enum test6 = !isIdentical(0.0, -0.0); + enum test7 = !isIdentical(real.nan, -real.nan); + enum test8 = !isIdentical(real.infinity, -real.infinity); + + assert(test6); + assert(test7); + assert(test8); +} + +@safe @nogc pure nothrow unittest +{ + assert( !isIdentical(1.2,1.3)); assert( isIdentical(0.0, 0.0)); assert( isIdentical(1.0, 1.0)); assert( isIdentical(real.infinity, real.infinity)); assert( isIdentical(-real.infinity, -real.infinity)); + assert( isIdentical(real.nan, real.nan)); assert(!isIdentical(0.0, -0.0)); assert(!isIdentical(real.nan, -real.nan)); assert(!isIdentical(real.infinity, -real.infinity)); } - /********************************* * Return 1 if sign bit of e is set, 0 if not. */ diff --git a/libphobos/src/std/path.d b/libphobos/src/std/path.d index 9dae2a6..5fb3156 100644 --- a/libphobos/src/std/path.d +++ b/libphobos/src/std/path.d @@ -311,7 +311,6 @@ if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || @safe unittest { import std.array; - import std.utf : byDchar; assert(rtrimDirSeparators("//abc//").array == "//abc"); assert(rtrimDirSeparators("//abc//"d).array == "//abc"d); @@ -329,7 +328,6 @@ if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || @safe unittest { import std.array; - import std.utf : byDchar; assert(trimDirSeparators("//abc//").array == "abc"); assert(trimDirSeparators("//abc//"d).array == "abc"d); diff --git a/libphobos/src/std/process.d b/libphobos/src/std/process.d index 2efbcaa..1cb2264 100644 --- a/libphobos/src/std/process.d +++ b/libphobos/src/std/process.d @@ -2815,6 +2815,10 @@ void kill(Pid pid, int codeOrSignal) else version (Posix) { import core.sys.posix.signal : kill; + if (pid.osHandle == Pid.invalid) + throw new ProcessException("Pid is invalid"); + if (pid.osHandle == Pid.terminated) + throw new ProcessException("Pid is already terminated"); if (kill(pid.osHandle, codeOrSignal) == -1) throw ProcessException.newFromErrno(); } @@ -2856,7 +2860,7 @@ void kill(Pid pid, int codeOrSignal) do { s = tryWait(pid); } while (!s.terminated); version (Windows) assert(s.status == 123); else version (Posix) assert(s.status == -SIGKILL); - assertThrown!ProcessException(kill(pid)); + assertThrown!ProcessException(kill(pid)); // Already terminated } @system unittest // wait() and kill() detached process diff --git a/libphobos/src/std/stdio.d b/libphobos/src/std/stdio.d index e844297..b0d96f2 100644 --- a/libphobos/src/std/stdio.d +++ b/libphobos/src/std/stdio.d @@ -2409,13 +2409,13 @@ void main() private struct ByLineCopy(Char, Terminator) { private: - import std.typecons : RefCounted, RefCountedAutoInitialize; + import std.typecons : SafeRefCounted, RefCountedAutoInitialize; /* Ref-counting stops the source range's ByLineCopyImpl * from getting out of sync after the range is copied, e.g. * when accessing range.front, then using std.range.take, * then accessing range.front again. */ - alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator), + alias Impl = SafeRefCounted!(ByLineCopyImpl!(Char, Terminator), RefCountedAutoInitialize.no); Impl impl; @@ -4626,7 +4626,7 @@ struct lines f = File to read lines from. terminator = Line separator (`'\n'` by default). */ - this(File f, dchar terminator = '\n') + this(File f, dchar terminator = '\n') @safe { this.f = f; this.terminator = terminator; @@ -4721,6 +4721,47 @@ struct lines } } +@safe unittest +{ + /* + As pointed out in <https://github.com/dlang/phobos/issues/10605>, + it's a pity that `byLine()` & co. aren't @safe to use yet. + + This is a first attempt at working towards that goal. + For now, this test doesn't do much; as there isn't much to do safely yet. + */ + auto deleteMe = testFilename(); + scope(exit) { imported!"std.file".remove(deleteMe); } + + // Setup + { + auto f = File(deleteMe, "w"); + scope(exit) { f.close(); } + foreach (i; 1 .. 11) + f.writeln(i); + } + + // Actual tests + { + auto f = File(deleteMe, "r"); + scope(exit) { f.close(); } + + auto myLines = lines(f); + foreach (string line; myLines) + continue; + + auto myByLineCopy = f.byLineCopy; // but cannot safely iterate yet + /* + still `@system`: + - cannot call `@system` function `std.stdio.File.ByLineCopy!(immutable(char), char).ByLineCopy.empty` + - cannot call `@system` function `std.stdio.File.ByLineCopy!(immutable(char), char).ByLineCopy.popFront` + - cannot call `@system` function `std.stdio.File.ByLineCopy!(immutable(char), char).ByLineCopy.front` + */ + //foreach (line; myByLineCopy) + // continue; + } +} + @system unittest { static import std.file; |