diff options
author | Iain Buclaw <ibuclaw@gdcproject.org> | 2025-03-12 12:04:59 +0100 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gdcproject.org> | 2025-03-12 15:38:28 +0100 |
commit | d63b52e059a7d77b98a2ef005920a85feb1e2446 (patch) | |
tree | bc3f8f9879d5b32aadaa0835508741a1b07471f0 | |
parent | 6e4045513d789587b2c7750e9016c7035b461299 (diff) | |
download | gcc-d63b52e059a7d77b98a2ef005920a85feb1e2446.zip gcc-d63b52e059a7d77b98a2ef005920a85feb1e2446.tar.gz gcc-d63b52e059a7d77b98a2ef005920a85feb1e2446.tar.bz2 |
libphobos: Merge upstream phobos 0faae92d6
Phobos changes:
- Import phobos v2.111.0-beta.1.
- Added `bitCast' function to `std.conv'.
- Added `readfln' and `File.readfln' functions to `std.stdio'.
- New procedural API for `std.sumtype'.
libphobos/ChangeLog:
* src/MERGE: Merge upstream phobos 0faae92d6.
* testsuite/libphobos.phobos/std_array.d: Regenerate.
* testsuite/libphobos.phobos/std_conv.d: Regenerate.
* testsuite/libphobos.phobos/std_functional.d: Regenerate.
* testsuite/libphobos.phobos/std_sumtype.d: Regenerate.
26 files changed, 1787 insertions, 440 deletions
diff --git a/libphobos/src/MERGE b/libphobos/src/MERGE index 9603e65..a5a685d 100644 --- a/libphobos/src/MERGE +++ b/libphobos/src/MERGE @@ -1,4 +1,4 @@ -1b242048c9db88c52cb0df6cd50c2b7455bedc01 +0faae92d62bdc1cc1982f0e9c65830ece1677289 The first line of this file holds the git revision number of the last merge done from the dlang/phobos repository. diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d index 8a3add3..f8e1c05 100644 --- a/libphobos/src/std/algorithm/iteration.d +++ b/libphobos/src/std/algorithm/iteration.d @@ -446,35 +446,21 @@ if (fun.length >= 1) auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) { - import std.meta : AliasSeq, staticMap; + import std.meta : staticMap; + import std.functional : adjoin; alias RE = ElementType!(Range); - static if (fun.length > 1) - { - import std.functional : adjoin; - import std.meta : staticIndexOf; - alias _funs = staticMap!(unaryFun, fun); - alias _fun = adjoin!_funs; + alias _funs = staticMap!(unaryFun, fun); + alias _fun = adjoin!_funs; - // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed - // accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC), - // this validation loop can be moved into a template. - foreach (f; _funs) - { - static assert(!is(typeof(f(RE.init)) == void), - "Mapping function(s) must not return void: " ~ _funs.stringof); - } - } - else + // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed + // accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC), + // this validation loop can be moved into a template. + foreach (f; _funs) { - alias _fun = unaryFun!fun; - alias _funs = AliasSeq!(_fun); - - // Do the validation separately for single parameters due to - // https://issues.dlang.org/show_bug.cgi?id=15777. - static assert(!is(typeof(_fun(RE.init)) == void), - "Mapping function(s) must not return void: " ~ _funs.stringof); + static assert(!is(typeof(f(RE.init)) == void), + "Mapping function(s) must not return void: " ~ _funs.stringof); } return MapResult!(_fun, Range)(r); diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d index fea7025..53ffb06 100644 --- a/libphobos/src/std/array.d +++ b/libphobos/src/std/array.d @@ -3571,18 +3571,11 @@ See_Also: $(LREF appender) struct Appender(A) if (isDynamicArray!A) { - import core.memory : GC; + import std.format.spec : FormatSpec; private alias T = ElementEncodingType!A; - private struct Data - { - size_t capacity; - Unqual!T[] arr; - bool tryExtendBlock = false; - } - - private Data* _data; + InPlaceAppender!A* impl; /** * Constructs an `Appender` with a given array. Note that this does not copy the @@ -3590,27 +3583,17 @@ 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) @trusted + this(A arr) @safe { - // initialize to a given array. - _data = new Data; - _data.arr = cast(Unqual!T[]) arr; //trusted - - if (__ctfe) - return; + impl = new InPlaceAppender!A(arr); + } - // 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))) + private void ensureInit() @safe + { + if (impl is null) { - immutable cap = arr.capacity; //trusted - // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed) - if (cap > arr.length) - arr.length = cap; + impl = new InPlaceAppender!A; } - _data.capacity = arr.length; } /** @@ -3623,14 +3606,10 @@ if (isDynamicArray!A) */ void reserve(size_t newCapacity) { - if (_data) + if (newCapacity != 0) { - if (newCapacity > _data.capacity) - ensureAddable(newCapacity - _data.arr.length); - } - else - { - ensureAddable(newCapacity); + ensureInit(); + impl.reserve(newCapacity); } } @@ -3641,11 +3620,11 @@ if (isDynamicArray!A) */ @property size_t capacity() const { - return _data ? _data.capacity : 0; + return impl ? impl.capacity : 0; } /// Returns: The number of elements appended. - @property size_t length() const => _data ? _data.arr.length : 0; + @property size_t length() const => (impl is null) ? 0 : impl.length; /** * Use opSlice() from now on. @@ -3653,29 +3632,219 @@ if (isDynamicArray!A) */ @property inout(T)[] data() inout { - return this[]; + return opSlice(); } /** * Returns: The managed array. */ - @property inout(T)[] opSlice() inout @trusted + @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 { /* @trusted operation: * casting Unqual!T[] to inout(T)[] */ - return cast(typeof(return))(_data ? _data.arr : null); + return cast(typeof(return)) arr; } // ensure we can add nelems elements, resizing as necessary private void ensureAddable(size_t nelems) { - if (!_data) - _data = new Data; - immutable len = _data.arr.length; + immutable len = arr.length; immutable reqlen = len + nelems; - if (_data.capacity >= reqlen) + if (_capacity >= reqlen) return; // need to increase capacity @@ -3683,17 +3852,17 @@ if (isDynamicArray!A) { static if (__traits(compiles, new Unqual!T[1])) { - _data.arr.length = reqlen; + arr.length = reqlen; } else { // avoid restriction of @disable this() - _data.arr = _data.arr[0 .. _data.capacity]; - foreach (i; _data.capacity .. reqlen) - _data.arr ~= Unqual!T.init; + arr = arr[0 .. _capacity]; + foreach (i; _capacity .. reqlen) + arr ~= Unqual!T.init; } - _data.arr = _data.arr[0 .. len]; - _data.capacity = reqlen; + arr = arr[0 .. len]; + _capacity = reqlen; } else { @@ -3701,11 +3870,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)(_data.capacity, reqlen); + auto newlen = appenderNewCapacity!(T.sizeof)(_capacity, reqlen); // first, try extending the current block - if (_data.tryExtendBlock) + if (tryExtendBlock) { - immutable u = (() @trusted => GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); + immutable u = (() @trusted => GC.extend(arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); if (u) { // extend worked, update the capacity @@ -3714,11 +3883,10 @@ if (isDynamicArray!A) // at large unused blocks. static if (hasIndirections!T) { - immutable addedSize = u - (_data.capacity * T.sizeof); - () @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }(); + immutable addedSize = u - (_capacity * T.sizeof); + () @trusted { memset(arr.ptr + _capacity, 0, addedSize); }(); } - - _data.capacity = u / T.sizeof; + _capacity = u / T.sizeof; return; } } @@ -3732,11 +3900,11 @@ if (isDynamicArray!A) ~ "available pointer range"); auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))(); - _data.capacity = bi.size / T.sizeof; + _capacity = bi.size / T.sizeof; + import core.stdc.string : memcpy; if (len) - () @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }(); - - _data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])(); + () @trusted { memcpy(bi.base, arr.ptr, len * T.sizeof); }(); + 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 @@ -3747,7 +3915,7 @@ if (isDynamicArray!A) memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof); }(); - _data.tryExtendBlock = true; + tryExtendBlock = true; // leave the old data, for safety reasons } } @@ -3763,13 +3931,13 @@ if (isDynamicArray!A) enum bool canPutConstRange = isInputRange!(Unqual!Range) && !isInputRange!Range && - is(typeof(Appender.init.put(Range.init.front))); + is(typeof(InPlaceAppender.init.put(Range.init.front))); } private template canPutRange(Range) { enum bool canPutRange = isInputRange!Range && - is(typeof(Appender.init.put(Range.init.front))); + is(typeof(InPlaceAppender.init.put(Range.init.front))); } /** @@ -3798,13 +3966,13 @@ if (isDynamicArray!A) import core.lifetime : emplace; ensureAddable(1); - immutable len = _data.arr.length; + immutable len = arr.length; - auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])(); + auto bigData = (() @trusted => arr.ptr[0 .. len + 1])(); auto itemUnqual = (() @trusted => & cast() item)(); emplace(&bigData[len], *itemUnqual); //We do this at the end, in case of exceptions - _data.arr = bigData; + arr = bigData; } } @@ -3848,16 +4016,16 @@ if (isDynamicArray!A) auto bigDataFun(size_t extra) { ensureAddable(extra); - return (() @trusted => _data.arr.ptr[0 .. _data.arr.length + extra])(); + return (() @trusted => arr.ptr[0 .. arr.length + extra])(); } auto bigData = bigDataFun(items.length); - immutable len = _data.arr.length; + immutable len = arr.length; immutable newlen = bigData.length; alias UT = Unqual!T; - static if (is(typeof(_data.arr[] = items[])) && + static if (is(typeof(arr[] = items[])) && !hasElaborateAssign!UT && isAssignable!(UT, ElementEncodingType!Range)) { bigData[len .. newlen] = items[]; @@ -3873,7 +4041,7 @@ if (isDynamicArray!A) } //We do this at the end, in case of exceptions - _data.arr = bigData; + arr = bigData; } else static if (isSomeChar!T && isSomeChar!(ElementType!Range) && !is(immutable T == immutable ElementType!Range)) @@ -3916,10 +4084,7 @@ if (isDynamicArray!A) */ void clear() @trusted pure nothrow { - if (_data) - { - _data.arr = _data.arr.ptr[0 .. 0]; - } + arr = arr.ptr[0 .. 0]; } /** @@ -3931,13 +4096,8 @@ if (isDynamicArray!A) void shrinkTo(size_t newlength) @trusted pure { import std.exception : enforce; - 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"); + enforce(newlength <= arr.length, "Attempting to shrink Appender with newlength > length"); + arr = arr.ptr[0 .. newlength]; } } @@ -3952,13 +4112,18 @@ if (isDynamicArray!A) * Returns: * A `string` if `writer` is not set; `void` otherwise. */ - string toString()() const + auto toString() const + { + return toStringImpl(Unqual!(typeof(this)).stringof, data); + } + + static auto toStringImpl(string typeName, const T[] arr) { import std.format.spec : singleSpec; - auto app = appender!string(); + InPlaceAppender!string app; auto spec = singleSpec("%s"); - immutable len = _data ? _data.arr.length : 0; + immutable len = arr.length; // different reserve lengths because each element in a // non-string-like array uses two extra characters for `, `. static if (isSomeString!A) @@ -3971,25 +4136,25 @@ if (isDynamicArray!A) // length, as it assumes each element is only one char app.reserve((len * 3) + 25); } - toString(app, spec); + toStringImpl(typeName, arr, app, spec); return app.data; } - import std.format.spec : FormatSpec; - - /// ditto - template toString(Writer) + void toString(Writer)(scope ref Writer w, scope const ref FormatSpec!char fmt) const if (isOutputRange!(Writer, char)) { - 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, ')'); - } + toStringImpl(Unqual!(typeof(this)).stringof, data, w, fmt); + } + + static void toStringImpl(Writer)(string typeName, const T[] data, scope ref Writer w, + scope const ref FormatSpec!char fmt) + { + import std.format.write : formatValue; + import std.range.primitives : put; + put(w, typeName); + put(w, '('); + formatValue(w, data, fmt); + put(w, ')'); } } @@ -4032,6 +4197,16 @@ 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 { @@ -4295,6 +4470,24 @@ unittest } /++ + 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/libphobos/src/std/bigint.d b/libphobos/src/std/bigint.d index a8b3897..7fea64c 100644 --- a/libphobos/src/std/bigint.d +++ b/libphobos/src/std/bigint.d @@ -265,10 +265,11 @@ public: } else static if (op=="*") { - if (y == 0) + if (y == 0 || data.isZero()) { sign = false; data = 0UL; + return this; } else { @@ -361,6 +362,29 @@ public: return this; } + // https://issues.dlang.org/show_bug.cgi?id=10565 +@safe unittest +{ + // Test cases from the issue + BigInt a = BigInt("0"); + BigInt b = BigInt("-0"); + BigInt c = BigInt("0") * -1; + BigInt d = BigInt("0") * -42; + BigInt e = BigInt("0"); e *= -1; + BigInt f = BigInt(c); + BigInt g = BigInt("0") * cast(byte) -1; + BigInt h = BigInt("0"); h *= BigInt("-1"); + BigInt i = BigInt("0"); i -= 2 * i; + BigInt j = BigInt("0"); j = -j; + // All of these should be zero and not negative + auto values = [a, b, c, d, e, f, g, h, i, j]; + foreach (val; values) + { + assert(val == 0, "BigInt value should be equal to zero"); + assert(!(val < 0), "BigInt zero should not be negative"); + } +} + /// @safe unittest { diff --git a/libphobos/src/std/checkedint.d b/libphobos/src/std/checkedint.d index 630ae41..120a976 100644 --- a/libphobos/src/std/checkedint.d +++ b/libphobos/src/std/checkedint.d @@ -2078,7 +2078,7 @@ struct ProperCompare /** Hook that reserves a special value as a "Not a Number" representative. For -signed integrals, the reserved value is `T.min`. For signed integrals, the +signed integrals, the reserved value is `T.min`. For unsigned integrals, the reserved value is `T.max`. The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must diff --git a/libphobos/src/std/container/dlist.d b/libphobos/src/std/container/dlist.d index 8f7df10..04b0cd5 100644 --- a/libphobos/src/std/container/dlist.d +++ b/libphobos/src/std/container/dlist.d @@ -451,7 +451,7 @@ iterating over the container are never invalidated. Returns: The number of elements inserted -Complexity: $(BIGOH log(n)) +Complexity: $(BIGOH m), where `m` is the length of `stuff` */ size_t insertFront(Stuff)(Stuff stuff) { diff --git a/libphobos/src/std/conv.d b/libphobos/src/std/conv.d index 5e0165c..65042b8 100644 --- a/libphobos/src/std/conv.d +++ b/libphobos/src/std/conv.d @@ -13,6 +13,7 @@ $(TR $(TD Generic) $(TD $(LREF parse) $(LREF to) $(LREF toChars) + $(LREF bitCast) )) $(TR $(TD Strings) $(TD $(LREF text) @@ -6047,3 +6048,38 @@ package enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; assert(toCtString!0 == "0"); assert(toCtString!123456 == "123456"); } + +/** + * Takes the raw bits of a value and reinterprets them as a different type. + * + * Params: + * T = the new type. + * value = the value to reinterpret. + * + * Returns: a reference to the reinterpreted value. + */ +pragma(inline, true) +ref T bitCast(T, S)(ref S value) +if (T.sizeof <= S.sizeof) +{ + return *cast(T*) &value; +} + +/// +@safe unittest +{ + uint n = 0xDEADBEEF; + + version (LittleEndian) + assert(n.bitCast!(ubyte[4]) == [0xEF, 0xBE, 0xAD, 0xDE]); + version (BigEndian) + assert(n.bitCast!(ubyte[4]) == [0xDE, 0xAD, 0xBE, 0xEF]); +} + +// Sizes must be compatible +@safe unittest +{ + uint n; + + assert(!__traits(compiles, n.bitCast!ulong)); +} diff --git a/libphobos/src/std/datetime/stopwatch.d b/libphobos/src/std/datetime/stopwatch.d index eedc0ea..1dc303f 100644 --- a/libphobos/src/std/datetime/stopwatch.d +++ b/libphobos/src/std/datetime/stopwatch.d @@ -166,7 +166,6 @@ public: Thread.sleep(usecs(1)); sw.reset(); - assert(sw.peek() < msecs(1)); assert(sw._timeStarted > before); assert(sw._timeStarted <= MonoTime.currTime); } diff --git a/libphobos/src/std/format/internal/floats.d b/libphobos/src/std/format/internal/floats.d index 88b9d22..d1d0c1b 100644 --- a/libphobos/src/std/format/internal/floats.d +++ b/libphobos/src/std/format/internal/floats.d @@ -1476,37 +1476,40 @@ if (is(T == float) || is(T == double) assertCTFEable!( { - // log2 is broken for x87-reals on some computers in CTFE - // the following tests excludes these computers from the tests - // (https://issues.dlang.org/show_bug.cgi?id=21757) - enum test = cast(int) log2(3.05e2312L); - static if (real.mant_dig == 64 && test == 7681) + static if (real.mant_dig == 64) // 80 bit reals { - auto f = FormatSpec!dchar(""); - f.spec = 'e'; - assert(printFloat(real.infinity, f) == "inf"); - assert(printFloat(10.0L, f) == "1.000000e+01"); - assert(printFloat(2.6080L, f) == "2.608000e+00"); - assert(printFloat(3.05e2312L, f) == "3.050000e+2312"); - - f.precision = 60; - assert(printFloat(2.65e-54L, f) == - "2.650000000000000000059009987400547013941028940935296547599415e-54"); - - /* - commented out, because CTFE is currently too slow for 5000 digits with extreme values - - f.precision = 5000; - auto result2 = printFloat(1.2119e-4822L, f); - assert(result2.length == 5008); - assert(result2[$ - 20 .. $] == "60729486595339e-4822"); - auto result3 = printFloat(real.min_normal, f); - assert(result3.length == 5008); - assert(result3[$ - 20 .. $] == "20781410082267e-4932"); - auto result4 = printFloat(real.min_normal.nextDown, f); - assert(result4.length == 5008); - assert(result4[$ - 20 .. $] == "81413263331006e-4932"); - */ + // log2 is broken for x87-reals on some computers in CTFE + // the following tests excludes these computers from the tests + // (https://issues.dlang.org/show_bug.cgi?id=21757) + enum test = cast(int) log2(3.05e2312L); + static if (test == 7681) + { + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(10.0L, f) == "1.000000e+01"); + assert(printFloat(2.6080L, f) == "2.608000e+00"); + assert(printFloat(3.05e2312L, f) == "3.050000e+2312"); + + f.precision = 60; + assert(printFloat(2.65e-54L, f) == + "2.650000000000000000059009987400547013941028940935296547599415e-54"); + + /* + commented out, because CTFE is currently too slow for 5000 digits with extreme values + + f.precision = 5000; + auto result2 = printFloat(1.2119e-4822L, f); + assert(result2.length == 5008); + assert(result2[$ - 20 .. $] == "60729486595339e-4822"); + auto result3 = printFloat(real.min_normal, f); + assert(result3.length == 5008); + assert(result3[$ - 20 .. $] == "20781410082267e-4932"); + auto result4 = printFloat(real.min_normal.nextDown, f); + assert(result4.length == 5008); + assert(result4[$ - 20 .. $] == "81413263331006e-4932"); + */ + } } }); } @@ -2149,39 +2152,42 @@ if (is(T == float) || is(T == double) assertCTFEable!( { - // log2 is broken for x87-reals on some computers in CTFE - // the following tests excludes these computers from the tests - // (https://issues.dlang.org/show_bug.cgi?id=21757) - enum test = cast(int) log2(3.05e2312L); - static if (real.mant_dig == 64 && test == 7681) + static if (real.mant_dig == 64) // 80 bit reals { - auto f = FormatSpec!dchar(""); - f.spec = 'f'; - assert(printFloat(real.infinity, f) == "inf"); - assert(printFloat(10.0L, f) == "10.000000"); - assert(printFloat(2.6080L, f) == "2.608000"); - auto result1 = printFloat(3.05e2312L, f); - assert(result1.length == 2320); - assert(result1[0 .. 20] == "30499999999999999999"); - - f.precision = 60; - assert(printFloat(2.65e-54L, f) == - "0.000000000000000000000000000000000000000000000000000002650000"); - - /* - commented out, because CTFE is currently too slow for 5000 digits with extreme values - - f.precision = 5000; - auto result2 = printFloat(1.2119e-4822L, f); - assert(result2.length == 5002); - assert(result2[$ - 20 .. $] == "60076763752233836613"); - auto result3 = printFloat(real.min_normal, f); - assert(result3.length == 5002); - assert(result3[$ - 20 .. $] == "47124010882722980874"); - auto result4 = printFloat(real.min_normal.nextDown, f); - assert(result4.length == 5002); - assert(result4[$ - 20 .. $] == "52925846892214823939"); - */ + // log2 is broken for x87-reals on some computers in CTFE + // the following tests excludes these computers from the tests + // (https://issues.dlang.org/show_bug.cgi?id=21757) + enum test = cast(int) log2(3.05e2312L); + static if (test == 7681) + { + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(10.0L, f) == "10.000000"); + assert(printFloat(2.6080L, f) == "2.608000"); + auto result1 = printFloat(3.05e2312L, f); + assert(result1.length == 2320); + assert(result1[0 .. 20] == "30499999999999999999"); + + f.precision = 60; + assert(printFloat(2.65e-54L, f) == + "0.000000000000000000000000000000000000000000000000000002650000"); + + /* + commented out, because CTFE is currently too slow for 5000 digits with extreme values + + f.precision = 5000; + auto result2 = printFloat(1.2119e-4822L, f); + assert(result2.length == 5002); + assert(result2[$ - 20 .. $] == "60076763752233836613"); + auto result3 = printFloat(real.min_normal, f); + assert(result3.length == 5002); + assert(result3[$ - 20 .. $] == "47124010882722980874"); + auto result4 = printFloat(real.min_normal.nextDown, f); + assert(result4.length == 5002); + assert(result4[$ - 20 .. $] == "52925846892214823939"); + */ + } } }); } @@ -2830,37 +2836,40 @@ if (is(T == float) || is(T == double) assertCTFEable!( { - // log2 is broken for x87-reals on some computers in CTFE - // the following tests excludes these computers from the tests - // (https://issues.dlang.org/show_bug.cgi?id=21757) - enum test = cast(int) log2(3.05e2312L); - static if (real.mant_dig == 64 && test == 7681) + static if (real.mant_dig == 64) // 80 bit reals { - auto f = FormatSpec!dchar(""); - f.spec = 'g'; - assert(printFloat(real.infinity, f) == "inf"); - assert(printFloat(10.0L, f) == "10"); - assert(printFloat(2.6080L, f) == "2.608"); - assert(printFloat(3.05e2312L, f) == "3.05e+2312"); - - f.precision = 60; - assert(printFloat(2.65e-54L, f) == - "2.65000000000000000005900998740054701394102894093529654759941e-54"); - - /* - commented out, because CTFE is currently too slow for 5000 digits with extreme values - - f.precision = 5000; - auto result2 = printFloat(1.2119e-4822L, f); - assert(result2.length == 5007); - assert(result2[$ - 20 .. $] == "26072948659534e-4822"); - auto result3 = printFloat(real.min_normal, f); - assert(result3.length == 5007); - assert(result3[$ - 20 .. $] == "72078141008227e-4932"); - auto result4 = printFloat(real.min_normal.nextDown, f); - assert(result4.length == 5007); - assert(result4[$ - 20 .. $] == "48141326333101e-4932"); - */ + // log2 is broken for x87-reals on some computers in CTFE + // the following tests excludes these computers from the tests + // (https://issues.dlang.org/show_bug.cgi?id=21757) + enum test = cast(int) log2(3.05e2312L); + static if (test == 7681) + { + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(10.0L, f) == "10"); + assert(printFloat(2.6080L, f) == "2.608"); + assert(printFloat(3.05e2312L, f) == "3.05e+2312"); + + f.precision = 60; + assert(printFloat(2.65e-54L, f) == + "2.65000000000000000005900998740054701394102894093529654759941e-54"); + + /* + commented out, because CTFE is currently too slow for 5000 digits with extreme values + + f.precision = 5000; + auto result2 = printFloat(1.2119e-4822L, f); + assert(result2.length == 5007); + assert(result2[$ - 20 .. $] == "26072948659534e-4822"); + auto result3 = printFloat(real.min_normal, f); + assert(result3.length == 5007); + assert(result3[$ - 20 .. $] == "72078141008227e-4932"); + auto result4 = printFloat(real.min_normal.nextDown, f); + assert(result4.length == 5007); + assert(result4[$ - 20 .. $] == "48141326333101e-4932"); + */ + } } }); } diff --git a/libphobos/src/std/format/internal/read.d b/libphobos/src/std/format/internal/read.d index 05d6adc..d2620e9 100644 --- a/libphobos/src/std/format/internal/read.d +++ b/libphobos/src/std/format/internal/read.d @@ -24,7 +24,7 @@ package(std.format): void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) { - import std.ascii : isDigit; + import std.ascii : isDigit, isWhite; import std.range.primitives : empty, front, popFront; switch (spec.spec) @@ -33,6 +33,9 @@ void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec case 'd': if (input.front == '+' || input.front == '-') input.popFront(); goto case 'u'; + case 's': + while (!input.empty && !isWhite(input.front)) input.popFront(); + break; case 'u': while (!input.empty && isDigit(input.front)) input.popFront(); break; diff --git a/libphobos/src/std/format/internal/write.d b/libphobos/src/std/format/internal/write.d index 6fd468d..bb6d878 100644 --- a/libphobos/src/std/format/internal/write.d +++ b/libphobos/src/std/format/internal/write.d @@ -902,13 +902,14 @@ if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) { import std.math.exponential : log2; - // log2 is broken for x87-reals on some computers in CTFE - // the following test excludes these computers from the test - // (https://issues.dlang.org/show_bug.cgi?id=21757) - enum test = cast(int) log2(3.05e2312L); - static if (real.mant_dig == 64 && test == 7681) // 80 bit reals + static if (real.mant_dig == 64) // 80 bit reals { - static assert(format!"%e"(real.max) == "1.189731e+4932"); + // log2 is broken for x87-reals on some computers in CTFE + // the following test excludes these computers from the test + // (https://issues.dlang.org/show_bug.cgi?id=21757) + enum test = cast(int) log2(3.05e2312L); + static if (test == 7681) + static assert(format!"%e"(real.max) == "1.189731e+4932"); } } diff --git a/libphobos/src/std/format/read.d b/libphobos/src/std/format/read.d index e2f9b94..2fa4e3e 100644 --- a/libphobos/src/std/format/read.d +++ b/libphobos/src/std/format/read.d @@ -377,6 +377,16 @@ if (!isType!fmt && isSomeString!(typeof(fmt))) assert(t[0] == 1 && t[1] == 2.125); } +@safe pure unittest +{ + string hello; + string world; + + assert("hello ignore world".formattedRead("%s %*s %s", hello, world) == 2); + assert(hello == "hello"); + assert(world == "world"); +} + // https://issues.dlang.org/show_bug.cgi?id=23600 @safe pure unittest { diff --git a/libphobos/src/std/functional.d b/libphobos/src/std/functional.d index b1b1382..2e1c6e9 100644 --- a/libphobos/src/std/functional.d +++ b/libphobos/src/std/functional.d @@ -51,6 +51,9 @@ $(TR $(TH Function Name) $(TH Description) $(TR $(TD $(LREF bind)) $(TD Passes the fields of a struct as arguments to a function. )) + $(TR $(TD $(LREF ctEval)) + $(TD Enforces the evaluation of an expression during compile-time. + )) )) Copyright: Copyright Andrei Alexandrescu 2008 - 2009. @@ -1886,16 +1889,21 @@ private template buildDelegate(F) @safe unittest { - static int inc(ref uint num) { + static int inc(ref int num) { num++; return 8675309; } - uint myNum = 0x1337; - struct S1 { int opCall() { inc(myNum); return myNum; } } - static assert(!is(typeof(&s1.opCall) == delegate)); + struct S1 + { + static int myNum = 0x1337; + static int opCall() { inc(myNum); return myNum; } + } + S1 s1; auto getvals1 = toDelegate(s1); + static assert(!is(typeof(&s1.opCall) == delegate)); + static assert( is(typeof(toDelegate(s1)) == delegate)); assert(getvals1() == 0x1338); } @@ -1924,15 +1932,18 @@ private template buildDelegate(F) assert(getvali() == 3); struct S1 { int opCall() { inc(myNum); return myNum; } } - static assert(!is(typeof(&s1.opCall) == delegate)); S1 s1; auto getvals1 = toDelegate(s1); + static assert(is(typeof(&s1.opCall) == delegate)); + static assert(is(typeof(getvals1) == delegate)); + assert(&s1.opCall is getvals1); assert(getvals1() == 4); struct S2 { static int opCall() { return 123456; } } - static assert(!is(typeof(&S2.opCall) == delegate)); S2 s2; - auto getvals2 =&S2.opCall; + auto getvals2 = toDelegate(s2); + static assert(!is(typeof(&S2.opCall) == delegate)); + static assert( is(typeof(getvals2) == delegate)); assert(getvals2() == 123456); /* test for attributes */ @@ -2167,3 +2178,50 @@ template bind(alias fun) static assert(!__traits(isRef, x)); }); } + +/** + * Enforces the evaluation of an expression during compile-time. + * + * Computes the value of an expression during compilation (CTFE). + * + * This is useful for call chains in functional programming + * where declaring an `enum` constant would require splitting + * the pipeline. + * + * Params: + * expr = expression to evaluate + * See_also: + * $(LINK https://dlang.org/spec/function.html#interpretation) + */ +enum ctEval(alias expr) = expr; + +/// +@safe unittest +{ + import std.math : abs; + + // No explicit `enum` needed. + float result = ctEval!(abs(-3)); + assert(result == 3); + + // Can be statically asserted. + static assert(ctEval!(abs(-4)) == 4); + static assert(ctEval!(abs( 9)) == 9); +} + +/// +@safe unittest +{ + import core.stdc.math : round; + import std.conv : to; + import std.math : abs, PI, sin; + + // `round` from the C standard library cannot be interpreted at compile + // time, because it has no available source code. However the function + // calls preceding `round` can be evaluated during compile time. + int result = ctEval!(abs(sin(1.0)) * 180 / PI) + .round() + .to!int(); + + assert(result == 48); +} diff --git a/libphobos/src/std/getopt.d b/libphobos/src/std/getopt.d index cb97eeb..1a90722 100644 --- a/libphobos/src/std/getopt.d +++ b/libphobos/src/std/getopt.d @@ -610,6 +610,23 @@ private template optionValidator(A...) alias optionValidator = message; } +private void handleConversion(R)(string option, string value, R* receiver, + size_t idx, string file = __FILE__, size_t line = __LINE__) +{ + import std.conv : to, ConvException; + import std.format : format; + try + { + *receiver = to!(typeof(*receiver))(value); + } + catch (ConvException e) + { + throw new ConvException(format("Argument '%s' at position '%u' could " + ~ "not be converted to type '%s' as required by option '%s'.", + value, idx, R.stringof, option), e, file, line); + } +} + @safe pure unittest { alias P = void*; @@ -864,7 +881,7 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, if (val.length) { // parse '--b=true/false' - *receiver = to!(typeof(*receiver))(val); + handleConversion(option, val, receiver, i); } else { @@ -888,15 +905,22 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, val = args[i]; args = args[0 .. i] ~ args[i + 1 .. $]; } - static if (is(typeof(*receiver) == enum)) + static if (is(typeof(*receiver) == enum) || + is(typeof(*receiver) == string)) { - *receiver = to!(typeof(*receiver))(val); + handleConversion(option, val, receiver, i); } else static if (is(typeof(*receiver) : real)) { // numeric receiver - if (incremental) ++*receiver; - else *receiver = to!(typeof(*receiver))(val); + if (incremental) + { + ++*receiver; + } + else + { + handleConversion(option, val, receiver, i); + } } else static if (is(typeof(*receiver) == string)) { @@ -936,12 +960,18 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, if (arraySep == "") { - *receiver ~= to!E(val); + E tmp; + handleConversion(option, val, &tmp, i); + *receiver ~= tmp; } else { - foreach (elem; val.splitter(arraySep).map!(a => to!E(a))()) - *receiver ~= elem; + foreach (elem; val.splitter(arraySep)) + { + E tmp; + handleConversion(option, elem, &tmp, i); + *receiver ~= tmp; + } } } else static if (isAssociativeArray!(typeof(*receiver))) @@ -961,7 +991,14 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'."); auto key = input[0 .. j]; auto value = input[j + 1 .. $]; - return tuple(to!K(key), to!V(value)); + + K k; + handleConversion("", key, &k, 0); + + V v; + handleConversion("", value, &v, 0); + + return tuple(k,v); } static void setHash(Range)(R receiver, Range range) @@ -1946,3 +1983,59 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, st ~ "information.\n"; assert(wanted == helpMsg); } + + +@safe unittest +{ + import std.conv : ConvException; + import std.string : indexOf; + + enum UniqueIdentifer { + a, + b + } + + UniqueIdentifer a; + + auto args = ["prog", "--foo", "HELLO"]; + try + { + auto t = getopt(args, "foo|f", &a); + assert(false, "Must not be reached, as \"HELLO\" cannot be converted" + ~ " to enum A."); + } + catch (ConvException e) + { + string str = () @trusted { return e.toString(); }(); + assert(str.indexOf("HELLO") != -1); + assert(str.indexOf("UniqueIdentifer") != -1); + assert(str.indexOf("foo") != -1); + } +} + +@safe unittest +{ + import std.conv : ConvException; + import std.string : indexOf; + + int a; + + auto args = ["prog", "--foo", "HELLO"]; + try + { + auto t = getopt(args, "foo|f", &a); + assert(false, "Must not be reached, as \"HELLO\" cannot be converted" + ~ " to an int"); + } + catch (ConvException e) + { + string str = () @trusted { return e.toString(); }(); + assert(str.indexOf("HELLO") != -1); + assert(str.indexOf("int") != -1); + assert(str.indexOf("foo") != -1); + } + + args = ["prog", "--foo", "1337"]; + getopt(args, "foo|f", &a); + assert(a == 1337); +} diff --git a/libphobos/src/std/math/operations.d b/libphobos/src/std/math/operations.d index d456e29..d14d9b3 100644 --- a/libphobos/src/std/math/operations.d +++ b/libphobos/src/std/math/operations.d @@ -1950,52 +1950,55 @@ if (isFloatingPoint!T) alias F = floatTraits!real; - // log2 is broken for x87-reals on some computers in CTFE - // the following test excludes these computers from the test - // (https://issues.dlang.org/show_bug.cgi?id=21757) - enum test = cast(int) log2(3.05e2312L); - static if (F.realFormat == RealFormat.ieeeExtended && test == 7681) + static if (F.realFormat == RealFormat.ieeeExtended) { - enum r1 = 1.0L; - enum bp1 = extractBitpattern(r1); - static assert(bp1.mantissa == 0x8000_0000_0000_0000L); - static assert(bp1.exponent == 0); - static assert(bp1.negative == false); - - enum r2 = real.max; - enum bp2 = extractBitpattern(r2); - static assert(bp2.mantissa == 0xffff_ffff_ffff_ffffL); - static assert(bp2.exponent == 16383); - static assert(bp2.negative == false); - - enum r3 = -1.5432e-3333L; - enum bp3 = extractBitpattern(r3); - static assert(bp3.mantissa == 0xc768_a2c7_a616_cc22L); - static assert(bp3.exponent == -11072); - static assert(bp3.negative == true); - - enum r4 = 0.0L.nextUp; - enum bp4 = extractBitpattern(r4); - static assert(bp4.mantissa == 0x0000_0000_0000_0001L); - static assert(bp4.exponent == -16382); - static assert(bp4.negative == false); - - enum r5 = -real.infinity; - enum bp5 = extractBitpattern(r5); - static assert(bp5.mantissa == 0); - static assert(bp5.exponent == 16384); - static assert(bp5.negative == true); - - enum r6 = real.nan; - enum bp6 = extractBitpattern(r6); - static assert(bp6.mantissa != 0); // we don't guarantee payloads - static assert(bp6.exponent == 16384); - static assert(bp6.negative == false); - - enum r7 = nextDown(0x1p+16383L); - enum bp7 = extractBitpattern(r7); - static assert(bp7.mantissa == 0xffff_ffff_ffff_ffffL); - static assert(bp7.exponent == 16382); - static assert(bp7.negative == false); + // log2 is broken for x87-reals on some computers in CTFE + // the following test excludes these computers from the test + // (https://issues.dlang.org/show_bug.cgi?id=21757) + enum test = cast(int) log2(3.05e2312L); + static if (test == 7681) + { + enum r1 = 1.0L; + enum bp1 = extractBitpattern(r1); + static assert(bp1.mantissa == 0x8000_0000_0000_0000L); + static assert(bp1.exponent == 0); + static assert(bp1.negative == false); + + enum r2 = real.max; + enum bp2 = extractBitpattern(r2); + static assert(bp2.mantissa == 0xffff_ffff_ffff_ffffL); + static assert(bp2.exponent == 16383); + static assert(bp2.negative == false); + + enum r3 = -1.5432e-3333L; + enum bp3 = extractBitpattern(r3); + static assert(bp3.mantissa == 0xc768_a2c7_a616_cc22L); + static assert(bp3.exponent == -11072); + static assert(bp3.negative == true); + + enum r4 = 0.0L.nextUp; + enum bp4 = extractBitpattern(r4); + static assert(bp4.mantissa == 0x0000_0000_0000_0001L); + static assert(bp4.exponent == -16382); + static assert(bp4.negative == false); + + enum r5 = -real.infinity; + enum bp5 = extractBitpattern(r5); + static assert(bp5.mantissa == 0); + static assert(bp5.exponent == 16384); + static assert(bp5.negative == true); + + enum r6 = real.nan; + enum bp6 = extractBitpattern(r6); + static assert(bp6.mantissa != 0); // we don't guarantee payloads + static assert(bp6.exponent == 16384); + static assert(bp6.negative == false); + + enum r7 = nextDown(0x1p+16383L); + enum bp7 = extractBitpattern(r7); + static assert(bp7.mantissa == 0xffff_ffff_ffff_ffffL); + static assert(bp7.exponent == 16382); + static assert(bp7.negative == false); + } } } diff --git a/libphobos/src/std/process.d b/libphobos/src/std/process.d index ca7880b..1a020c8 100644 --- a/libphobos/src/std/process.d +++ b/libphobos/src/std/process.d @@ -4315,14 +4315,14 @@ version (Posix) import core.sys.posix.stdlib; } -private void toAStringz(in string[] a, const(char)**az) +private const(char)** toAStringz(in string[] a) { import std.string : toStringz; - foreach (string s; a) - { - *az++ = toStringz(s); - } - *az = null; + auto p = (new const(char)*[1 + a.length]).ptr; + foreach (i, string s; a) + p[i] = toStringz(s); + p[a.length] = null; + return p; } @@ -4452,45 +4452,17 @@ extern(C) private int execv_(in string pathname, in string[] argv) { - import core.exception : OutOfMemoryError; - import std.exception : enforce; - auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); - enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); - scope(exit) core.stdc.stdlib.free(argv_); - - toAStringz(argv, argv_); - - return execv(pathname.tempCString(), argv_); + return execv(pathname.tempCString(), toAStringz(argv)); } private int execve_(in string pathname, in string[] argv, in string[] envp) { - import core.exception : OutOfMemoryError; - import std.exception : enforce; - auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); - enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); - scope(exit) core.stdc.stdlib.free(argv_); - auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); - enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process."); - scope(exit) core.stdc.stdlib.free(envp_); - - toAStringz(argv, argv_); - toAStringz(envp, envp_); - - return execve(pathname.tempCString(), argv_, envp_); + return execve(pathname.tempCString(), toAStringz(argv), toAStringz(envp)); } private int execvp_(in string pathname, in string[] argv) { - import core.exception : OutOfMemoryError; - import std.exception : enforce; - auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); - enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); - scope(exit) core.stdc.stdlib.free(argv_); - - toAStringz(argv, argv_); - - return execvp(pathname.tempCString(), argv_); + return execvp(pathname.tempCString(), toAStringz(argv)); } private int execvpe_(in string pathname, in string[] argv, in string[] envp) @@ -4532,19 +4504,7 @@ version (Posix) } else version (Windows) { - import core.exception : OutOfMemoryError; - import std.exception : enforce; - auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); - enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); - scope(exit) core.stdc.stdlib.free(argv_); - auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); - enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process."); - scope(exit) core.stdc.stdlib.free(envp_); - - toAStringz(argv, argv_); - toAStringz(envp, envp_); - - return execvpe(pathname.tempCString(), argv_, envp_); + return execvpe(pathname.tempCString(), toAStringz(argv), toAStringz(envp)); } else { diff --git a/libphobos/src/std/random.d b/libphobos/src/std/random.d index c221024..fb4e546 100644 --- a/libphobos/src/std/random.d +++ b/libphobos/src/std/random.d @@ -1772,11 +1772,74 @@ else } } +version (linux) +{ + // `getrandom()` was introduced in Linux 3.17. + + // Shim for missing bindings in druntime + version (none) + import core.sys.linux.sys.random : getrandom; + else + { + import core.sys.posix.sys.types : ssize_t; + extern extern(C) ssize_t getrandom( + void* buf, + size_t buflen, + uint flags, + ) @system nothrow @nogc; + } +} + +version (Windows) +{ + pragma(lib, "Bcrypt.lib"); + + private bool bcryptGenRandom(T)(out T result) @trusted + { + import core.sys.windows.windef : PUCHAR, ULONG; + import core.sys.windows.ntdef : NT_SUCCESS; + import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG; + + const gotRandom = BCryptGenRandom( + null, + cast(PUCHAR) &result, + ULONG(T.sizeof), + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + + return NT_SUCCESS(gotRandom); + } +} + /** A "good" seed for initializing random number engines. Initializing with $(D_PARAM unpredictableSeed) makes engines generate different random number sequences every run. +This function utilizes the system $(I cryptographically-secure pseudo-random +number generator (CSPRNG)) or $(I pseudo-random number generator (PRNG)) +where available and implemented (currently `arc4random` on applicable BSD +systems, `getrandom` on Linux or `BCryptGenRandom` on Windows) to generate +“high quality” pseudo-random numbers – if possible. +As a consequence, calling it may block under certain circumstances (typically +during early boot when the system's entropy pool has not yet been +initialized). + +On x86 CPU models which support the `RDRAND` instruction, that will be used +when no more specialized randomness source is implemented. + +In the future, further platform-specific PRNGs may be incorporated. + +Warning: +$(B This function must not be used for cryptographic purposes.) +Despite being implemented for certain targets, there are no guarantees +that it sources its randomness from a CSPRNG. +The implementation also includes a fallback option that provides very little +randomness and is used when no better source of randomness is available or +integrated on the target system. +As written earlier, this function only aims to provide randomness for seeding +ordinary (non-cryptographic) PRNG engines. + Returns: A single unsigned integer seed value, different on each successive call Note: @@ -1788,7 +1851,37 @@ how excellent the source of entropy is. */ @property uint unpredictableSeed() @trusted nothrow @nogc { - version (AnyARC4Random) + version (linux) + { + uint buffer; + + /* + getrandom(2): + If the _urandom_ source has been initialized, reads of up to + 256 bytes will always return as many bytes as requested and + will not be interrupted by signals. No such guarantees apply + for larger buffer sizes. + */ + static assert(buffer.sizeof <= 256); + + const status = (() @trusted => getrandom(&buffer, buffer.sizeof, 0))(); + assert(status == buffer.sizeof); + + return buffer; + } + else version (Windows) + { + uint result; + if (!bcryptGenRandom!uint(result)) + { + version (none) + return fallbackSeed(); + else + assert(false, "BCryptGenRandom() failed."); + } + return result; + } + else version (AnyARC4Random) { return arc4random(); } @@ -1837,7 +1930,37 @@ if (isUnsigned!UIntType) /// ditto @property UIntType unpredictableSeed() @nogc nothrow @trusted { - version (AnyARC4Random) + version (linux) + { + UIntType buffer; + + /* + getrandom(2): + If the _urandom_ source has been initialized, reads of up to + 256 bytes will always return as many bytes as requested and + will not be interrupted by signals. No such guarantees apply + for larger buffer sizes. + */ + static assert(buffer.sizeof <= 256); + + const status = (() @trusted => getrandom(&buffer, buffer.sizeof, 0))(); + assert(status == buffer.sizeof); + + return buffer; + } + else version (Windows) + { + UIntType result; + if (!bcryptGenRandom!UIntType(result)) + { + version (none) + return fallbackSeed(); + else + assert(false, "BCryptGenRandom() failed."); + } + return result; + } + else version (AnyARC4Random) { static if (UIntType.sizeof <= uint.sizeof) { diff --git a/libphobos/src/std/range/interfaces.d b/libphobos/src/std/range/interfaces.d index 6d55d414..64b82e2 100644 --- a/libphobos/src/std/range/interfaces.d +++ b/libphobos/src/std/range/interfaces.d @@ -31,7 +31,7 @@ $(BOOKTABLE , $(TR $(TD $(LREF RandomAccessFinite)) $(TD Wrapper for finite random-access ranges. )) - $(TR $(TD $(LREF RandomAccessAssignable)) + $(TR $(TD $(LREF RandomFiniteAssignable)) $(TD Wrapper for finite random-access ranges with assignable elements. )) $(TR $(TD $(LREF RandomAccessInfinite)) diff --git a/libphobos/src/std/range/package.d b/libphobos/src/std/range/package.d index 3a135eb..8258059 100644 --- a/libphobos/src/std/range/package.d +++ b/libphobos/src/std/range/package.d @@ -1042,7 +1042,7 @@ if (Ranges.length > 0 && // We do this separately to avoid invoking `empty` needlessly. // While not recommended, a range may depend on side effects of // `empty` call. - foreach (i, ref v; input) if (!v.empty) + foreach (i, ref v; source) if (!v.empty) { frontIndex = i; static if (bidirectional) backIndex = i+1; @@ -1056,7 +1056,7 @@ if (Ranges.length > 0 && static foreach_reverse (i; 1 .. R.length + 1) { if (i <= frontIndex + 1) return; - if (!input[i-1].empty) + if (!source[i-1].empty) { backIndex = i; return; @@ -11019,6 +11019,23 @@ auto only()() static assert(!__traits(compiles, () { r3[0] = 789; })); } +// https://github.com/dlang/phobos/issues/10561 +@safe unittest +{ + static struct Range + { + private int i; + + enum bool empty = false; + int front() => i; + void popFront() { ++i; } + } + import std.algorithm; + + assert(Range().take(10).filter!"a>8".chain(only(100)).equal([9,100])); + assert((new Range()).take(10).filter!"a>8".chain(only(100)).equal([9,100])); +} + /** Iterate over `range` with an attached index variable. diff --git a/libphobos/src/std/stdio.d b/libphobos/src/std/stdio.d index 4734c1b7..82a1392 100644 --- a/libphobos/src/std/stdio.d +++ b/libphobos/src/std/stdio.d @@ -18,6 +18,7 @@ $(TR $(TD Reading) $(TD $(MYREF chunks) $(MYREF lines) $(MYREF readf) + $(MYREF readfln) $(MYREF readln) )) $(TR $(TD Writing) $(TD @@ -2094,6 +2095,85 @@ $(CONSOLE "Unexpected '\\n' when converting from type LockingTextReader to type int"); } + /** + Reads a line from the file and parses it using $(REF formattedRead, std,format,read). + + Params: + format = The $(MREF_ALTTEXT format string, std,format). When passed as a + compile-time argument, the string will be statically checked against the + argument types passed. + data = Items to be read. + + Returns: Same as `formattedRead`: the number of variables filled. If the + input ends early, this number will be less that the number of variables + provided. + + Example: + --- + // sum_rows.d + void main() + { + import std.stdio; + auto f = File("input"); + int a, b, c; + while (f.readfln("%d %d %d", a, b, c) == 3) + { + writeln(a + b + c); + } + } + --- + $(CONSOLE +% cat << EOF > input +1 2 3 +4 5 6 +7 8 9 +EOF +% rdmd sum_rows.d +6 +15 +24 + ) + */ + uint readfln(alias format, Data...)(auto ref Data data) + if (isSomeString!(typeof(format))) + { + import std.format : checkFormatException; + + alias e = checkFormatException!(format, Data); + static assert(!e, e); + return this.readfln(format, data); + } + + /// ditto + uint readfln(Data...)(scope const(char)[] format, auto ref Data data) + { + import std.format.read : formattedRead; + import std.string : stripRight; + + string line = this.readln.stripRight("\r\n"); + return formattedRead(line, format, data); + } + + @system unittest + { + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); + scope(exit) std.file.remove(deleteme); + string s; + auto f = File(deleteme); + f.readfln!"%s"(s); + assert(s == "hello", "["~s~"]"); + f.readfln("%s", s); + assert(s == "world", "["~s~"]"); + + bool b1, b2; + f.readfln("%s", b1); + f.readfln("%s", b2); + assert(b1 == true && b2 == false); + } + /** Returns a temporary file by calling $(CSTDIO tmpfile). Note that the created file has no $(LREF name).*/ @@ -4489,6 +4569,70 @@ if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && } } +/** +Reads a line from `stdin` and parses it using $(REF formattedRead, std,format,read). + +Params: + format = The $(MREF_ALTTEXT format string, std,format). When passed as a + compile-time argument, the string will be statically checked against the + argument types passed. + data = Items to be read. + +Returns: Same as `formattedRead`: the number of variables filled. If the +input ends early, this number will be less that the number of variables +provided. + +Example: +--- +// sum_rows.d +void main() +{ + import std.stdio; + int a, b, c; + while (readfln("%d %d %d", a, b, c) == 3) + { + writeln(a + b + c); + } +} +--- +$(CONSOLE +% cat << EOF > input +1 2 3 +4 5 6 +7 8 9 +EOF +% rdmd sum_rows.d < input +6 +15 +24 +) +*/ +uint readfln(alias format, Data...)(auto ref Data data) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(format, Data); + static assert(!e, e); + return .readfln(format, data); +} + +/// ditto +uint readfln(Data...)(scope const(char)[] format, auto ref Data data) +{ + return stdin.readfln(format, data); +} + +@system unittest +{ + float f; + string s; + char c; + int n; + if (false) readfln("%f %s %c %d", f, s, c, n); + if (false) readfln!"%f %s %c %d"(f, s, c, n); + +} + /* * Convenience function that forwards to `core.sys.posix.stdio.fopen` * (to `_wfopen` on Windows) diff --git a/libphobos/src/std/sumtype.d b/libphobos/src/std/sumtype.d index ad29428..ab6ade0 100644 --- a/libphobos/src/std/sumtype.d +++ b/libphobos/src/std/sumtype.d @@ -254,6 +254,8 @@ private enum hasPostblit(T) = __traits(hasPostblit, T); private enum isInout(T) = is(T == inout); +private enum memberName(size_t tid) = "values_" ~ toCtString!tid; + /** * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a * single value from any of a specified set of types. @@ -290,45 +292,45 @@ private: union Storage { - // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068 - template memberName(T) - if (IndexOf!(T, Types) >= 0) - { - enum tid = IndexOf!(T, Types); - mixin("enum memberName = `values_", toCtString!tid, "`;"); - } - static foreach (T; Types) + static foreach (tid, T; Types) { - mixin("T ", memberName!T, ";"); + /+ + Giving these fields individual names makes it possible to use brace + initialization for Storage. + +/ + mixin("T ", memberName!tid, ";"); } } Storage storage; - Tag tag; + static if (Types.length > 1) + Tag tag; + else + enum Tag tag = 0; - /* Accesses the value stored in a SumType. + /* Accesses the value stored in a SumType by its index. * * This method is memory-safe, provided that: * * 1. A SumType's tag is always accurate. - * 2. A SumType cannot be assigned to in @safe code if that assignment - * could cause unsafe aliasing. + * 2. A SumType's value cannot be unsafely aliased in @safe code. * * All code that accesses a SumType's tag or storage directly, including * @safe code in this module, must be manually checked to ensure that it * does not violate either of the above requirements. */ @trusted - ref inout(T) get(T)() inout - if (IndexOf!(T, Types) >= 0) + // Explicit return type omitted + // Workaround for https://github.com/dlang/dmd/issues/20549 + ref getByIndex(size_t tid)() inout + if (tid < Types.length) { - enum tid = IndexOf!(T, Types); assert(tag == tid, - "This `" ~ SumType.stringof ~ - "` does not contain a(n) `" ~ T.stringof ~ "`" + "This `" ~ SumType.stringof ~ "`" ~ + "does not contain a(n) `" ~ Types[tid].stringof ~ "`" ); - return __traits(getMember, storage, Storage.memberName!T); + return storage.tupleof[tid]; } public: @@ -363,14 +365,15 @@ public: static if (isCopyable!T) { // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 - __traits(getMember, storage, Storage.memberName!T) = __ctfe ? value : forward!value; + storage.tupleof[tid] = __ctfe ? value : forward!value; } else { - __traits(getMember, storage, Storage.memberName!T) = forward!value; + storage.tupleof[tid] = forward!value; } - tag = tid; + static if (Types.length > 1) + tag = tid; } static if (isCopyable!(const(T))) @@ -380,8 +383,9 @@ public: /// ditto this(const(T) value) const { - __traits(getMember, storage, Storage.memberName!T) = value; - tag = tid; + storage.tupleof[tid] = value; + static if (Types.length > 1) + tag = tid; } } } @@ -397,8 +401,9 @@ public: /// ditto this(immutable(T) value) immutable { - __traits(getMember, storage, Storage.memberName!T) = value; - tag = tid; + storage.tupleof[tid] = value; + static if (Types.length > 1) + tag = tid; } } } @@ -415,8 +420,9 @@ public: this(Value)(Value value) inout if (is(Value == DeducedParameterType!(inout(T)))) { - __traits(getMember, storage, Storage.memberName!T) = value; - tag = tid; + storage.tupleof[tid] = value; + static if (Types.length > 1) + tag = tid; } } } @@ -442,16 +448,16 @@ public: storage = other.match!((ref value) { alias OtherTypes = Map!(InoutOf, Types); enum tid = IndexOf!(typeof(value), OtherTypes); - alias T = Types[tid]; mixin("inout(Storage) newStorage = { ", - Storage.memberName!T, ": value", + memberName!tid, ": value", " };"); return newStorage; }); - tag = other.tag; + static if (Types.length > 1) + tag = other.tag; } } else @@ -462,16 +468,17 @@ public: this(ref SumType other) { storage = other.match!((ref value) { - alias T = typeof(value); + enum tid = IndexOf!(typeof(value), Types); mixin("Storage newStorage = { ", - Storage.memberName!T, ": value", + memberName!tid, ": value", " };"); return newStorage; }); - tag = other.tag; + static if (Types.length > 1) + tag = other.tag; } } else @@ -487,16 +494,16 @@ public: storage = other.match!((ref value) { alias OtherTypes = Map!(ConstOf, Types); enum tid = IndexOf!(typeof(value), OtherTypes); - alias T = Types[tid]; mixin("const(Storage) newStorage = { ", - Storage.memberName!T, ": value", + memberName!tid, ": value", " };"); return newStorage; }); - tag = other.tag; + static if (Types.length > 1) + tag = other.tag; } } else @@ -512,16 +519,16 @@ public: storage = other.match!((ref value) { alias OtherTypes = Map!(ImmutableOf, Types); enum tid = IndexOf!(typeof(value), OtherTypes); - alias T = Types[tid]; mixin("immutable(Storage) newStorage = { ", - Storage.memberName!T, ": value", + memberName!tid, ": value", " };"); return newStorage; }); - tag = other.tag; + static if (Types.length > 1) + tag = other.tag; } } else @@ -637,18 +644,19 @@ public: { // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 mixin("Storage newStorage = { ", - Storage.memberName!T, ": __ctfe ? rhs : forward!rhs", + memberName!tid, ": __ctfe ? rhs : forward!rhs", " };"); } else { mixin("Storage newStorage = { ", - Storage.memberName!T, ": forward!rhs", + memberName!tid, ": forward!rhs", " };"); } storage = newStorage; - tag = tid; + static if (Types.length > 1) + tag = tid; return this; } @@ -1146,7 +1154,7 @@ version (D_BetterC) {} else alias MySum = SumType!(ubyte, void*[2]); MySum x = [null, cast(void*) 0x12345678]; - void** p = &x.get!(void*[2])[1]; + void** p = &x.getByIndex!1[1]; x = ubyte(123); assert(*p != cast(void*) 0x12345678); @@ -1178,8 +1186,8 @@ version (D_BetterC) {} else catch (Exception e) {} assert( - (x.tag == 0 && x.get!A.value == 123) || - (x.tag == 1 && x.get!B.value == 456) + (x.tag == 0 && x.getByIndex!0.value == 123) || + (x.tag == 1 && x.getByIndex!1.value == 456) ); } @@ -1238,8 +1246,8 @@ version (D_BetterC) {} else SumType!(S[1]) x = [S(0)]; SumType!(S[1]) y = x; - auto xval = x.get!(S[1])[0].n; - auto yval = y.get!(S[1])[0].n; + auto xval = x.getByIndex!0[0].n; + auto yval = y.getByIndex!0[0].n; assert(xval != yval); } @@ -1324,8 +1332,8 @@ version (D_BetterC) {} else SumType!S y; y = x; - auto xval = x.get!S.n; - auto yval = y.get!S.n; + auto xval = x.getByIndex!0.n; + auto yval = y.getByIndex!0.n; assert(xval != yval); } @@ -1399,8 +1407,8 @@ version (D_BetterC) {} else SumType!S x = S(); SumType!S y = x; - auto xval = x.get!S.n; - auto yval = y.get!S.n; + auto xval = x.getByIndex!0.n; + auto yval = y.getByIndex!0.n; assert(xval != yval); } @@ -1562,6 +1570,13 @@ version (D_BetterC) {} else enum result = test(); } +// https://github.com/dlang/phobos/issues/10563 +// Do not waste space for tag if sumtype has only single type +@safe unittest +{ + static assert(SumType!int.sizeof == int.sizeof); +} + /// True if `T` is an instance of the `SumType` template, otherwise false. private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); @@ -1815,7 +1830,7 @@ class MatchException : Exception template canMatch(alias handler, Ts...) if (Ts.length > 0) { - enum canMatch = is(typeof((ref Ts args) => handler(args))); + enum canMatch = is(typeof(auto ref (ref Ts args) => handler(args))); } /// @@ -1840,6 +1855,21 @@ if (Ts.length > 0) assert(canMatch!(OverloadSet.fun, double)); } +// Allows returning non-copyable types by ref +// https://github.com/dlang/phobos/issues/10647 +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + static NoCopy lvalue; + static ref handler(int _) => lvalue; + + assert(canMatch!(handler, int)); +} + // Like aliasSeqOf!(iota(n)), but works in BetterC private template Iota(size_t n) { @@ -1872,10 +1902,10 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) * argument's tag, so there's no need for TagTuple. */ enum handlerArgs(size_t caseId) = - "args[0].get!(SumTypes[0].Types[" ~ toCtString!caseId ~ "])()"; + "args[0].getByIndex!(" ~ toCtString!caseId ~ ")()"; alias valueTypes(size_t caseId) = - typeof(args[0].get!(SumTypes[0].Types[caseId])()); + typeof(args[0].getByIndex!(caseId)()); enum numCases = SumTypes[0].Types.length; } @@ -1901,9 +1931,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) template getType(size_t i) { - enum tid = tags[i]; - alias T = SumTypes[i].Types[tid]; - alias getType = typeof(args[i].get!T()); + alias getType = typeof(args[i].getByIndex!(tags[i])()); } alias valueTypes = Map!(getType, Iota!(tags.length)); @@ -2128,8 +2156,7 @@ private template handlerArgs(size_t caseId, typeCounts...) { handlerArgs = AliasSeq!( handlerArgs, - "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~ - ".Types[" ~ toCtString!(tags[i]) ~ "])(), " + "args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), " ); } } @@ -2393,7 +2420,7 @@ version (D_Exceptions) (ref double d) { d *= 2; } ); - assert(value.get!double.isClose(6.28)); + assert(value.getByIndex!1.isClose(6.28)); } // Unreachable handlers @@ -2615,6 +2642,417 @@ version (D_Exceptions) })); } +/** + * Checks whether a `SumType` contains a value of a given type. + * + * The types must match exactly, without implicit conversions. + * + * Params: + * T = the type to check for. + */ +template has(T) +{ + /** + * The actual `has` function. + * + * Params: + * self = the `SumType` to check. + * + * Returns: true if `self` contains a `T`, otherwise false. + */ + bool has(Self)(auto ref Self self) + if (isSumType!Self) + { + return self.match!checkType; + } + + // Helper to avoid redundant template instantiations + private bool checkType(Value)(ref Value value) + { + return is(Value == T); + } +} + +/// Basic usage +@safe unittest +{ + SumType!(string, double) example = "hello"; + + assert( example.has!string); + assert(!example.has!double); + + // If T isn't part of the SumType, has!T will always return false. + assert(!example.has!int); +} + +/// With type qualifiers +@safe unittest +{ + alias Example = SumType!(string, double); + + Example m = "mutable"; + const Example c = "const"; + immutable Example i = "immutable"; + + assert( m.has!string); + assert(!m.has!(const(string))); + assert(!m.has!(immutable(string))); + + assert(!c.has!string); + assert( c.has!(const(string))); + assert(!c.has!(immutable(string))); + + assert(!i.has!string); + assert(!i.has!(const(string))); + assert( i.has!(immutable(string))); +} + +/// As a predicate +version (D_BetterC) {} else +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.algorithm.comparison : equal; + + alias Example = SumType!(string, double); + + auto arr = [ + Example("foo"), + Example(0), + Example("bar"), + Example(1), + Example(2), + Example("baz") + ]; + + auto strings = arr.filter!(has!string); + auto nums = arr.filter!(has!double); + + assert(strings.equal([Example("foo"), Example("bar"), Example("baz")])); + assert(nums.equal([Example(0), Example(1), Example(2)])); +} + +// Non-copyable types +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy x; + + assert(x.has!NoCopy); +} + +/** + * Accesses a `SumType`'s value. + * + * The value must be of the specified type. Use [has] to check. + * + * Params: + * T = the type of the value being accessed. + */ +template get(T) +{ + /** + * The actual `get` function. + * + * Params: + * self = the `SumType` whose value is being accessed. + * + * Returns: the `SumType`'s value. + */ + auto ref T get(Self)(auto ref Self self) + if (isSumType!Self) + { + import std.typecons : No; + + static if (__traits(isRef, self)) + return self.match!(getLvalue!(No.try_, T)); + else + return self.match!(getRvalue!(No.try_, T)); + } +} + +/// Basic usage +@safe unittest +{ + SumType!(string, double) example1 = "hello"; + SumType!(string, double) example2 = 3.14; + + assert(example1.get!string == "hello"); + assert(example2.get!double == 3.14); +} + +/// With type qualifiers +@safe unittest +{ + alias Example = SumType!(string, double); + + Example m = "mutable"; + const(Example) c = "const"; + immutable(Example) i = "immutable"; + + assert(m.get!string == "mutable"); + assert(c.get!(const(string)) == "const"); + assert(i.get!(immutable(string)) == "immutable"); +} + +/// As a predicate +version (D_BetterC) {} else +@safe unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.comparison : equal; + + alias Example = SumType!(string, double); + + auto arr = [Example(0), Example(1), Example(2)]; + auto values = arr.map!(get!double); + + assert(values.equal([0, 1, 2])); +} + +// Non-copyable types +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy lvalue; + auto rvalue() => SumType!NoCopy(); + + assert(lvalue.get!NoCopy == NoCopy()); + assert(rvalue.get!NoCopy == NoCopy()); +} + +// Immovable rvalues +@safe unittest +{ + auto rvalue() => const(SumType!string)("hello"); + + assert(rvalue.get!(const(string)) == "hello"); +} + +// Nontrivial rvalues at compile time +@safe unittest +{ + static struct ElaborateCopy + { + this(this) {} + } + + enum rvalue = SumType!ElaborateCopy(); + enum ctResult = rvalue.get!ElaborateCopy; + + assert(ctResult == ElaborateCopy()); +} + +/** + * Attempt to access a `SumType`'s value. + * + * If the `SumType` does not contain a value of the specified type, an + * exception is thrown. + * + * Params: + * T = the type of the value being accessed. + */ +version (D_Exceptions) +template tryGet(T) +{ + /** + * The actual `tryGet` function. + * + * Params: + * self = the `SumType` whose value is being accessed. + * + * Throws: `MatchException` if the value does not have the expected type. + * + * Returns: the `SumType`'s value. + */ + auto ref T tryGet(Self)(auto ref Self self) + if (isSumType!Self) + { + import std.typecons : Yes; + + static if (__traits(isRef, self)) + return self.match!(getLvalue!(Yes.try_, T)); + else + return self.match!(getRvalue!(Yes.try_, T)); + } +} + +/// Basic usage +version (D_Exceptions) +@safe unittest +{ + SumType!(string, double) example = "hello"; + + assert(example.tryGet!string == "hello"); + + double result = double.nan; + try + result = example.tryGet!double; + catch (MatchException e) + result = 0; + + // Exception was thrown + assert(result == 0); +} + +/// With type qualifiers +version (D_Exceptions) +@safe unittest +{ + import std.exception : assertThrown; + + const(SumType!(string, double)) example = "const"; + + // Qualifier mismatch; throws exception + assertThrown!MatchException(example.tryGet!string); + // Qualifier matches; no exception + assert(example.tryGet!(const(string)) == "const"); +} + +/// As a predicate +version (D_BetterC) {} else +@safe unittest +{ + import std.algorithm.iteration : map, sum; + import std.functional : pipe; + import std.exception : assertThrown; + + alias Example = SumType!(string, double); + + auto arr1 = [Example(0), Example(1), Example(2)]; + auto arr2 = [Example("foo"), Example("bar"), Example("baz")]; + + alias trySum = pipe!(map!(tryGet!double), sum); + + assert(trySum(arr1) == 0 + 1 + 2); + assertThrown!MatchException(trySum(arr2)); +} + +// Throws if requested type is impossible +version (D_Exceptions) +@safe unittest +{ + import std.exception : assertThrown; + + SumType!int x; + + assertThrown!MatchException(x.tryGet!string); +} + +// Non-copyable types +version (D_Exceptions) +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy lvalue; + auto rvalue() => SumType!NoCopy(); + + assert(lvalue.tryGet!NoCopy == NoCopy()); + assert(rvalue.tryGet!NoCopy == NoCopy()); +} + +// Immovable rvalues +version (D_Exceptions) +@safe unittest +{ + auto rvalue() => const(SumType!string)("hello"); + + assert(rvalue.tryGet!(const(string)) == "hello"); +} + +// Nontrivial rvalues at compile time +version (D_Exceptions) +@safe unittest +{ + static struct ElaborateCopy + { + this(this) {} + } + + enum rvalue = SumType!ElaborateCopy(); + enum ctResult = rvalue.tryGet!ElaborateCopy; + + assert(ctResult == ElaborateCopy()); +} + +private template failedGetMessage(Expected, Actual) +{ + static if (Expected.stringof == Actual.stringof) + { + enum expectedStr = __traits(fullyQualifiedName, Expected); + enum actualStr = __traits(fullyQualifiedName, Actual); + } + else + { + enum expectedStr = Expected.stringof; + enum actualStr = Actual.stringof; + } + + enum failedGetMessage = + "Tried to get `" ~ expectedStr ~ "`" ~ + " but found `" ~ actualStr ~ "`"; +} + +private template getLvalue(Flag!"try_" try_, T) +{ + ref T getLvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + return value; + } + else + { + static if (try_) + throw new MatchException(failedGetMessage!(T, Value)); + else + assert(false, failedGetMessage!(T, Value)); + } + } +} + +private template getRvalue(Flag!"try_" try_, T) +{ + T getRvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + import core.lifetime : move; + + // Move if possible; otherwise fall back to copy + static if (is(typeof(move(value)))) + { + static if (isCopyable!Value) + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 + return __ctfe ? value : move(value); + else + return move(value); + } + else + return value; + } + else + { + static if (try_) + throw new MatchException(failedGetMessage!(T, Value)); + else + assert(false, failedGetMessage!(T, Value)); + } + } +} + private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) diff --git a/libphobos/src/std/typecons.d b/libphobos/src/std/typecons.d index 989ccba..d7f86d1 100644 --- a/libphobos/src/std/typecons.d +++ b/libphobos/src/std/typecons.d @@ -447,19 +447,6 @@ private: assert(ptr.bar.val == 7); } -// Used in Tuple.toString -private template sharedToString(alias field) -if (is(typeof(field) == shared)) -{ - static immutable sharedToString = typeof(field).stringof; -} - -private template sharedToString(alias field) -if (!is(typeof(field) == shared)) -{ - alias sharedToString = field; -} - private enum bool distinctFieldNames(names...) = __traits(compiles, { static foreach (__name; names) @@ -1307,11 +1294,11 @@ if (distinctFieldNames!(Specs)) * Returns: * The string representation of this `Tuple`. */ - string toString()() const + string toString()() { import std.array : appender; auto app = appender!string(); - this.toString((const(char)[] chunk) => app ~= chunk); + toString((const(char)[] chunk) => app ~= chunk); return app.data; } @@ -1333,14 +1320,14 @@ if (distinctFieldNames!(Specs)) * sink = A `char` accepting delegate * fmt = A $(REF FormatSpec, std,format) */ - void toString(DG)(scope DG sink) const + void toString(DG)(scope DG sink) { auto f = FormatSpec!char(); toString(sink, f); } /// ditto - void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) const + void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) { import std.format : format, FormatException; import std.format.write : formattedWrite; @@ -1355,20 +1342,12 @@ if (distinctFieldNames!(Specs)) { sink(fmt.sep); } - // TODO: Change this once formattedWrite() works for shared objects. - static if (is(Type == class) && is(Type == shared)) - { - sink(Type.stringof); - } - else - { - formattedWrite(sink, fmt.nested, this.field[i]); - } + formattedWrite(sink, fmt.nested, this.field[i]); } } else { - formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand)); + formattedWrite(sink, fmt.nested, this.expand); } } else if (fmt.spec == 's') @@ -1383,15 +1362,8 @@ if (distinctFieldNames!(Specs)) { sink(separator); } - // TODO: Change this once format() works for shared objects. - static if (is(Type == class) && is(Type == shared)) - { - sink(Type.stringof); - } - else - { - sink(format!("%(%s%)")(only(field[i]))); - } + // Among other things, using "only" causes string-fields to be inside quotes in the result + sink.formattedWrite!("%(%s%)")(only(field[i])); } sink(footer); } @@ -1812,7 +1784,36 @@ private template ReverseTupleSpecs(T...) Tuple!(int, shared A) nosh; nosh[0] = 5; assert(nosh[0] == 5 && nosh[1] is null); - assert(nosh.to!string == "Tuple!(int, shared(A))(5, shared(A))"); + + assert(nosh.to!string == "Tuple!(int, shared(A))(5, null)"); + } + { + // Shared, without fmt.sep + import std.format; + import std.algorithm.searching; + static class A {int i = 1;} + Tuple!(int, shared A) nosh; + nosh[0] = 5; + assert(nosh[0] == 5 && nosh[1] is null); + + // needs trusted, because Object.toString() isn't @safe + auto f = ()@trusted => format!("%(%s, %s%)")(nosh); + assert(f() == "5, null"); + nosh[1] = new shared A(); + // Currently contains the mangled type name + // 5, const(std.typecons.__unittest_L1750_C7.A) + // This assert is not necessarily to prescribe this behaviour, only to signal if there is a breaking change. + // See https://github.com/dlang/phobos/issues/9811 + auto s = f(); + assert(s.canFind("__unittest_L")); + assert(s.endsWith(".A)")); + } + { + static struct A {} + Tuple!(int, shared A*) nosh; + nosh[0] = 5; + assert(nosh[0] == 5 && nosh[1] is null); + assert(nosh.to!string == "Tuple!(int, shared(A*))(5, null)"); } { Tuple!(int, string) t; @@ -1821,6 +1822,40 @@ private template ReverseTupleSpecs(T...) assert(t[0] == 10 && t[1] == "str"); assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string); } + /* https://github.com/dlang/phobos/issues/9811 + * Note: This is just documenting current behaviour, dependent on `std.format` implementation + * details. None of this is defined in a spec or should be regarded as rigid. + */ + { + static struct X + { + /** Usually, toString() should be const where possible. + * But as long as the tuple is also non-const, this will work + */ + string toString() + { + return "toString non-const"; + } + } + assert(tuple(X()).to!string == "Tuple!(X)(toString non-const)"); + const t = tuple(X()); + // This is an implementation detail of `format` + // if the tuple is const, than non-const toString will not be called + assert(t.to!string == "const(Tuple!(X))(const(X)())"); + + static struct X2 + { + string toString() const /* const toString will work in more cases */ + { + return "toString const"; + } + } + assert(tuple(X2()).to!string == "Tuple!(X2)(toString const)"); + const t2 = tuple(X2()); + // This is an implementation detail of `format` + // if the tuple is const, than non-const toString will not be called + assert(t2.to!string == "const(Tuple!(X2))(toString const)"); + } { Tuple!(int, "a", double, "b") x; static assert(x.a.offsetof == x[0].offsetof); diff --git a/libphobos/testsuite/libphobos.phobos/std_array.d b/libphobos/testsuite/libphobos.phobos/std_array.d index 1370d08..87ea0f0 100644 --- a/libphobos/testsuite/libphobos.phobos/std_array.d +++ b/libphobos/testsuite/libphobos.phobos/std_array.d @@ -451,6 +451,23 @@ int[] a = [ 1, 2 ]; auto app2 = appender(a); app2.put(3); + app2.put([ 4, 5, 6 ]); + assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); +} + +@safe pure nothrow unittest +{ + import std.array; + + 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); assert(app2.length == 3); app2.put([ 4, 5, 6 ]); assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); diff --git a/libphobos/testsuite/libphobos.phobos/std_conv.d b/libphobos/testsuite/libphobos.phobos/std_conv.d index 01f6fb7..5e9636b 100644 --- a/libphobos/testsuite/libphobos.phobos/std_conv.d +++ b/libphobos/testsuite/libphobos.phobos/std_conv.d @@ -509,3 +509,15 @@ assert(toChars!(16, char, LetterCase.upper)(255U).equal("FF")); } +@safe unittest +{ + import std.conv; + + uint n = 0xDEADBEEF; + + version (LittleEndian) + assert(n.bitCast!(ubyte[4]) == [0xEF, 0xBE, 0xAD, 0xDE]); + version (BigEndian) + assert(n.bitCast!(ubyte[4]) == [0xDE, 0xAD, 0xBE, 0xEF]); +} + diff --git a/libphobos/testsuite/libphobos.phobos/std_functional.d b/libphobos/testsuite/libphobos.phobos/std_functional.d index 3bfab45..50b37f3 100644 --- a/libphobos/testsuite/libphobos.phobos/std_functional.d +++ b/libphobos/testsuite/libphobos.phobos/std_functional.d @@ -358,3 +358,36 @@ pure @safe @nogc nothrow unittest assert(overForty.equal(["Bob", "Eve"])); } +@safe unittest +{ + import std.functional; + + import std.math : abs; + + // No explicit `enum` needed. + float result = ctEval!(abs(-3)); + assert(result == 3); + + // Can be statically asserted. + static assert(ctEval!(abs(-4)) == 4); + static assert(ctEval!(abs( 9)) == 9); +} + +@safe unittest +{ + import std.functional; + + import core.stdc.math : round; + import std.conv : to; + import std.math : abs, PI, sin; + + // `round` from the C standard library cannot be interpreted at compile + // time, because it has no available source code. However the function + // calls preceding `round` can be evaluated during compile time. + int result = ctEval!(abs(sin(1.0)) * 180 / PI) + .round() + .to!int(); + + assert(result == 48); +} + diff --git a/libphobos/testsuite/libphobos.phobos/std_sumtype.d b/libphobos/testsuite/libphobos.phobos/std_sumtype.d index 2a7eaf3..7084e98 100644 --- a/libphobos/testsuite/libphobos.phobos/std_sumtype.d +++ b/libphobos/testsuite/libphobos.phobos/std_sumtype.d @@ -299,3 +299,156 @@ assert(!canMatch!(handleInt, string)); } +@safe unittest +{ + import std.sumtype; + + SumType!(string, double) example = "hello"; + + assert( example.has!string); + assert(!example.has!double); + + // If T isn't part of the SumType, has!T will always return false. + assert(!example.has!int); +} + +@safe unittest +{ + import std.sumtype; + + alias Example = SumType!(string, double); + + Example m = "mutable"; + const Example c = "const"; + immutable Example i = "immutable"; + + assert( m.has!string); + assert(!m.has!(const(string))); + assert(!m.has!(immutable(string))); + + assert(!c.has!string); + assert( c.has!(const(string))); + assert(!c.has!(immutable(string))); + + assert(!i.has!string); + assert(!i.has!(const(string))); + assert( i.has!(immutable(string))); +} + +@safe unittest +{ + import std.sumtype; + + import std.algorithm.iteration : filter; + import std.algorithm.comparison : equal; + + alias Example = SumType!(string, double); + + auto arr = [ + Example("foo"), + Example(0), + Example("bar"), + Example(1), + Example(2), + Example("baz") + ]; + + auto strings = arr.filter!(has!string); + auto nums = arr.filter!(has!double); + + assert(strings.equal([Example("foo"), Example("bar"), Example("baz")])); + assert(nums.equal([Example(0), Example(1), Example(2)])); +} + +@safe unittest +{ + import std.sumtype; + + SumType!(string, double) example1 = "hello"; + SumType!(string, double) example2 = 3.14; + + assert(example1.get!string == "hello"); + assert(example2.get!double == 3.14); +} + +@safe unittest +{ + import std.sumtype; + + alias Example = SumType!(string, double); + + Example m = "mutable"; + const(Example) c = "const"; + immutable(Example) i = "immutable"; + + assert(m.get!string == "mutable"); + assert(c.get!(const(string)) == "const"); + assert(i.get!(immutable(string)) == "immutable"); +} + +@safe unittest +{ + import std.sumtype; + + import std.algorithm.iteration : map; + import std.algorithm.comparison : equal; + + alias Example = SumType!(string, double); + + auto arr = [Example(0), Example(1), Example(2)]; + auto values = arr.map!(get!double); + + assert(values.equal([0, 1, 2])); +} + +@safe unittest +{ + import std.sumtype; + + SumType!(string, double) example = "hello"; + + assert(example.tryGet!string == "hello"); + + double result = double.nan; + try + result = example.tryGet!double; + catch (MatchException e) + result = 0; + + // Exception was thrown + assert(result == 0); +} + +@safe unittest +{ + import std.sumtype; + + import std.exception : assertThrown; + + const(SumType!(string, double)) example = "const"; + + // Qualifier mismatch; throws exception + assertThrown!MatchException(example.tryGet!string); + // Qualifier matches; no exception + assert(example.tryGet!(const(string)) == "const"); +} + +@safe unittest +{ + import std.sumtype; + + import std.algorithm.iteration : map, sum; + import std.functional : pipe; + import std.exception : assertThrown; + + alias Example = SumType!(string, double); + + auto arr1 = [Example(0), Example(1), Example(2)]; + auto arr2 = [Example("foo"), Example("bar"), Example("baz")]; + + alias trySum = pipe!(map!(tryGet!double), sum); + + assert(trySum(arr1) == 0 + 1 + 2); + assertThrown!MatchException(trySum(arr2)); +} + |