[committed] libphobos: Merge upstream phobos 0faae92d6

Message ID 20250312144226.196045-1-ibuclaw@gdcproject.org
State New
Headers
Series [committed] libphobos: Merge upstream phobos 0faae92d6 |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap fail Patch failed to apply
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap fail Patch failed to apply

Commit Message

Iain Buclaw March 12, 2025, 2:42 p.m. UTC
  Hi,

This patch merges the D standard library with upstream phobos 0faae92d6.

Synchronizing with the upstream release of v2.111.0-beta.1.

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

Bootstrapped and regression tested on x86_64-linux-gnu/-m32, committed
to mainline.

Regards,
Iain.

---
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.
---
 libphobos/src/MERGE                           |   2 +-
 libphobos/src/std/algorithm/iteration.d       |  34 +-
 libphobos/src/std/array.d                     | 387 +++++++++---
 libphobos/src/std/bigint.d                    |  26 +-
 libphobos/src/std/checkedint.d                |   2 +-
 libphobos/src/std/container/dlist.d           |   2 +-
 libphobos/src/std/conv.d                      |  36 ++
 libphobos/src/std/datetime/stopwatch.d        |   1 -
 libphobos/src/std/format/internal/floats.d    | 193 +++---
 libphobos/src/std/format/internal/read.d      |   5 +-
 libphobos/src/std/format/internal/write.d     |  13 +-
 libphobos/src/std/format/read.d               |  10 +
 libphobos/src/std/functional.d                |  72 ++-
 libphobos/src/std/getopt.d                    | 111 +++-
 libphobos/src/std/math/operations.d           |  95 +--
 libphobos/src/std/process.d                   |  60 +-
 libphobos/src/std/random.d                    | 127 +++-
 libphobos/src/std/range/interfaces.d          |   2 +-
 libphobos/src/std/range/package.d             |  21 +-
 libphobos/src/std/stdio.d                     | 144 +++++
 libphobos/src/std/sumtype.d                   | 560 ++++++++++++++++--
 libphobos/src/std/typecons.d                  | 109 ++--
 .../testsuite/libphobos.phobos/std_array.d    |  17 +
 .../testsuite/libphobos.phobos/std_conv.d     |  12 +
 .../libphobos.phobos/std_functional.d         |  33 ++
 .../testsuite/libphobos.phobos/std_sumtype.d  | 153 +++++
 26 files changed, 1787 insertions(+), 440 deletions(-)
  

Patch

diff --git a/libphobos/src/MERGE b/libphobos/src/MERGE
index 9603e65aa42..a5a685de236 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 8a3add3b733..f8e1c05b658 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 fea70258ebf..53ffb06905f 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
 {
@@ -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`.
diff --git a/libphobos/src/std/bigint.d b/libphobos/src/std/bigint.d
index a8b38979597..7fea64cd6d5 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 630ae4153a8..120a976f3c7 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 8f7df105c83..04b0cd538dd 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 5e0165cb609..65042b81095 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 eedc0ea79c9..1dc303f61e2 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 88b9d22d7d9..d1d0c1bc399 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 05d6adc6318..d2620e903d0 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 6fd468d315e..bb6d878c839 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 e2f9b944a18..2fa4e3eb2df 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 b1b1382280d..2e1c6e9ae67 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 cb97eebe31b..1a9072207f5 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 d456e29adb6..d14d9b32ed3 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 ca7880bbe9e..1a020c88fef 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 c2210248edf..fb4e5469088 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 6d55d4149c7..64b82e2a621 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 3a135ebba51..82580599a7d 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 4734c1b7165..82a13929f51 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 ad2942873a6..ab6ade0e1d8 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 989ccba042c..d7f86d17aed 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 1370d089943..87ea0f0c447 100644
--- a/libphobos/testsuite/libphobos.phobos/std_array.d
+++ b/libphobos/testsuite/libphobos.phobos/std_array.d
@@ -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;
diff --git a/libphobos/testsuite/libphobos.phobos/std_conv.d b/libphobos/testsuite/libphobos.phobos/std_conv.d
index 01f6fb7e256..5e9636be9eb 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 3bfab45658e..50b37f35d4c 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 2a7eaf35f12..7084e98e88f 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));
+}
+