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.
This commit is contained in:
Iain Buclaw 2025-03-12 12:04:59 +01:00
parent 6e4045513d
commit d63b52e059
26 changed files with 1773 additions and 426 deletions

View file

@ -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.

View file

@ -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)
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)
{
import std.functional : adjoin;
import std.meta : staticIndexOf;
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),
static assert(!is(typeof(f(RE.init)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}
}
else
{
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);
}
return MapResult!(_fun, Range)(r);

View file

@ -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
impl = new InPlaceAppender!A(arr);
}
if (__ctfe)
return;
// We want to use up as much of the block the array is in as possible.
// if we consume all the block that we can, then array appending is
// safe WRT built-in append, and we can use the entire block.
// We only do this for mutable types that can be extended.
static if (isMutable!T && is(typeof(arr.length = size_t.max)))
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
{
@ -4294,6 +4469,24 @@ unittest
assert(app2.capacity >= 5);
}
/++
Convenience function that returns a $(LREF InPlaceAppender) instance,
optionally initialized with `array`.
+/
package(std) InPlaceAppender!A inPlaceAppender(A)()
if (isDynamicArray!A)
{
return InPlaceAppender!A(null);
}
/// ditto
package(std) InPlaceAppender!(E[]) inPlaceAppender(A : E[], E)(auto ref A array)
{
static assert(!isStaticArray!A || __traits(isRef, array),
"Cannot create InPlaceAppender from an rvalue static array");
return InPlaceAppender!(E[])(array);
}
/++
Convenience function that returns an $(LREF Appender) instance,
optionally initialized with `array`.

View file

@ -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
{

View file

@ -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

View file

@ -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)
{

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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");
// 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");
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
/*
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");
*/
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");
// 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");
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
/*
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");
*/
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");
// 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");
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
/*
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");
*/
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");
*/
}
}
});
}

View file

@ -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;

View file

@ -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");
}
}

View file

@ -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
{

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
// 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 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 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 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 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 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);
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);
}
}
}

View file

@ -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
{

View file

@ -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)
{

View file

@ -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))

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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);

View file

@ -438,6 +438,23 @@
assert(b == [1, 0, 0, 0, 5]);
}
@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);
app2.put([ 4, 5, 6 ]);
assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]);
}
@safe pure nothrow unittest
{
import std.array;

View file

@ -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]);
}

View file

@ -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);
}

View file

@ -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));
}