From 7973da975eac7cf5155a140e4cb4f68d5cbb1eb9 Mon Sep 17 00:00:00 2001
From: Ralph Amissah <ralph.amissah@gmail.com>
Date: Sun, 4 Jun 2023 20:24:27 -0400
Subject: make set_depends (dyaml update)

---
 src/ext_depends/D-YAML.meta                        |    2 +-
 src/ext_depends/D-YAML/.github/workflows/d.yml     |   20 +-
 src/ext_depends/D-YAML/.gitignore                  |    1 +
 src/ext_depends/D-YAML/dub.json                    |   12 +
 src/ext_depends/D-YAML/source/dyaml/composer.d     |   38 +-
 src/ext_depends/D-YAML/source/dyaml/constructor.d  |    4 +-
 src/ext_depends/D-YAML/source/dyaml/emitter.d      |   17 +-
 src/ext_depends/D-YAML/source/dyaml/escapes.d      |   16 +-
 src/ext_depends/D-YAML/source/dyaml/exception.d    |   19 +-
 src/ext_depends/D-YAML/source/dyaml/loader.d       |   14 +-
 src/ext_depends/D-YAML/source/dyaml/node.d         |  263 +-
 src/ext_depends/D-YAML/source/dyaml/resolver.d     |   10 +-
 src/ext_depends/D-YAML/source/dyaml/scanner.d      |   52 +-
 src/ext_depends/D-YAML/source/dyaml/serializer.d   |    2 +-
 src/ext_depends/D-YAML/source/dyaml/stdsumtype.d   | 2627 ++++++++++++++++++++
 src/ext_depends/D-YAML/source/dyaml/test/tokens.d  |    2 +-
 .../D-YAML/test/data/emojianchor.canonical         |    5 +
 src/ext_depends/D-YAML/test/data/emojianchor.data  |    2 +
 .../D-YAML/test/data/invalid-anchor-1.loader-error |    1 -
 .../D-YAML/test/data/invalid-anchor-2.loader-error |    8 -
 .../D-YAML/test/data/invalid-anchor.loader-error   |    8 +
 21 files changed, 2958 insertions(+), 165 deletions(-)
 create mode 100644 src/ext_depends/D-YAML/source/dyaml/stdsumtype.d
 create mode 100644 src/ext_depends/D-YAML/test/data/emojianchor.canonical
 create mode 100644 src/ext_depends/D-YAML/test/data/emojianchor.data
 delete mode 100644 src/ext_depends/D-YAML/test/data/invalid-anchor-1.loader-error
 delete mode 100644 src/ext_depends/D-YAML/test/data/invalid-anchor-2.loader-error
 create mode 100644 src/ext_depends/D-YAML/test/data/invalid-anchor.loader-error

(limited to 'src')

diff --git a/src/ext_depends/D-YAML.meta b/src/ext_depends/D-YAML.meta
index a0cc85c..c9a2d17 100644
--- a/src/ext_depends/D-YAML.meta
+++ b/src/ext_depends/D-YAML.meta
@@ -1,3 +1,3 @@
-D-YAML e157571e
+D-YAML 2c915b3f
 https://github.com/dlang-community/D-YAML
 Boost Software License 1.0 (BSL-1.0)
diff --git a/src/ext_depends/D-YAML/.github/workflows/d.yml b/src/ext_depends/D-YAML/.github/workflows/d.yml
index 08f583f..b14a069 100644
--- a/src/ext_depends/D-YAML/.github/workflows/d.yml
+++ b/src/ext_depends/D-YAML/.github/workflows/d.yml
@@ -16,20 +16,23 @@ jobs:
           - dmd-beta
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 2
+    - uses: dlang-community/setup-dlang@v1
       with:
         compiler: ${{ matrix.dc }}
     - name: 'Test'
       run: |
+        dub test -c unittest-dip1000
         dub test --build=unittest-cov
         bash <(curl -s https://codecov.io/bash)
   examples:
     runs-on: ubuntu-latest
     needs: build
     steps:
-    - uses: actions/checkout@v2
-    - uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
+    - uses: actions/checkout@v3
+    - uses: dlang-community/setup-dlang@v1
       with:
         compiler: dmd-latest
     - name: 'Build Examples'
@@ -41,13 +44,14 @@ jobs:
         dub build dyaml:resolver
         dub build dyaml:testsuite
         dub build dyaml:tojson
+        dub build dyaml:tokens
         dub build dyaml:yaml_gen
         dub build dyaml:yaml_stats
   ninja:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
+    - uses: actions/checkout@v3
+    - uses: dlang-community/setup-dlang@v1
       with:
         compiler: dmd-latest
     - name: 'Install dependencies'
@@ -63,8 +67,8 @@ jobs:
     runs-on: ubuntu-latest
     needs: build
     steps:
-    - uses: actions/checkout@v2
-    - uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
+    - uses: actions/checkout@v3
+    - uses: dlang-community/setup-dlang@v1
       with:
         compiler: dmd-latest
     - name: 'Run YAML test suite'
diff --git a/src/ext_depends/D-YAML/.gitignore b/src/ext_depends/D-YAML/.gitignore
index 1a1aec4..ce69e30 100644
--- a/src/ext_depends/D-YAML/.gitignore
+++ b/src/ext_depends/D-YAML/.gitignore
@@ -13,6 +13,7 @@
 /examples/getting_started/main.obj
 /cdc.obj
 /unittest
+*.pdb
 
 # Backups #
 ###########
diff --git a/src/ext_depends/D-YAML/dub.json b/src/ext_depends/D-YAML/dub.json
index 07ee17e..601f902 100644
--- a/src/ext_depends/D-YAML/dub.json
+++ b/src/ext_depends/D-YAML/dub.json
@@ -11,12 +11,24 @@
     },
     "homepage": "https://github.com/dlang-community/D-YAML",
     "copyright": "Copyright © 2011-2018, Ferdinand Majerech",
+    "configurations": [
+        { "name": "library"  },
+        { "name": "unittest" },
+        {
+            "name": "unittest-dip1000",
+            "dflags": [ "-preview=dip1000" ],
+            "dependencies": {
+                "tinyendian":          { "version": "*", "dflags" : [ "-preview=dip1000" ] },
+            }
+        }
+    ],
     "subPackages": [
         "examples/constructor",
         "examples/getting_started",
         "examples/representer",
         "examples/resolver",
         "examples/tojson",
+        "examples/tokens",
         "examples/yaml_bench",
         "examples/yaml_gen",
         "examples/yaml_stats",
diff --git a/src/ext_depends/D-YAML/source/dyaml/composer.d b/src/ext_depends/D-YAML/source/dyaml/composer.d
index c000b02..e7b083a 100644
--- a/src/ext_depends/D-YAML/source/dyaml/composer.d
+++ b/src/ext_depends/D-YAML/source/dyaml/composer.d
@@ -16,6 +16,7 @@ import std.algorithm;
 import std.array;
 import std.conv;
 import std.exception;
+import std.format;
 import std.range;
 import std.typecons;
 
@@ -357,12 +358,18 @@ struct Composer
                 merge(*pairAppender, flatten(node[0], startEvent.startMark, node[1],
                                              pairAppenderLevel + 1, nodeAppenderLevel));
             }
-            auto numUnique = pairAppender.data.dup
-                                .sort!((x,y) => x.key > y.key)
-                                .uniq!((x,y) => x.key == y.key)
-                                .walkLength;
-            enforce(numUnique == pairAppender.data.length,
-                    new ComposerException("Duplicate key found in mapping", parser_.front.startMark));
+
+            auto sorted = pairAppender.data.dup.sort!((x,y) => x.key > y.key);
+            if (sorted.length) {
+                foreach (index, const ref value; sorted[0 .. $ - 1].enumerate)
+                    if (value.key == sorted[index + 1].key) {
+                        const message = () @trusted {
+                            return format("Key '%s' appears multiple times in mapping (first: %s)",
+                                          value.key.get!string, value.key.startMark);
+                        }();
+                        throw new ComposerException(message, sorted[index + 1].key.startMark);
+                    }
+            }
 
             Node node = constructNode(startEvent.startMark, parser_.front.endMark,
                                           tag, pairAppender.data.dup);
@@ -373,3 +380,22 @@ struct Composer
             return node;
         }
 }
+
+// Provide good error message on multiple keys (which JSON supports)
+@safe unittest
+{
+    import dyaml.loader : Loader;
+
+    const str = `{
+    "comment": "This is a common technique",
+    "name": "foobar",
+    "comment": "To write down comments pre-JSON5"
+}`;
+
+    try
+        auto node = Loader.fromString(str).load();
+    catch (ComposerException exc)
+        assert(exc.message() ==
+               "Key 'comment' appears multiple times in mapping " ~
+               "(first: file <unknown>,line 2,column 5)\nfile <unknown>,line 4,column 5");
+}
diff --git a/src/ext_depends/D-YAML/source/dyaml/constructor.d b/src/ext_depends/D-YAML/source/dyaml/constructor.d
index bc1d75c..4cd1546 100644
--- a/src/ext_depends/D-YAML/source/dyaml/constructor.d
+++ b/src/ext_depends/D-YAML/source/dyaml/constructor.d
@@ -481,7 +481,7 @@ Node.Pair[] constructOrderedMap(const Node[] nodes) @safe
 
     //Detect duplicates.
     //TODO this should be replaced by something with deterministic memory allocation.
-    auto keys = redBlackTree!Node();
+    auto keys = new RedBlackTree!Node();
     foreach(ref pair; pairs)
     {
         enforce(!(pair.key in keys),
@@ -600,7 +600,7 @@ Node.Pair[] constructMap(Node.Pair[] pairs) @safe
 {
     //Detect duplicates.
     //TODO this should be replaced by something with deterministic memory allocation.
-    auto keys = redBlackTree!Node();
+    auto keys = new RedBlackTree!Node();
     foreach(ref pair; pairs)
     {
         enforce(!(pair.key in keys),
diff --git a/src/ext_depends/D-YAML/source/dyaml/emitter.d b/src/ext_depends/D-YAML/source/dyaml/emitter.d
index 5cf6a92..5aafc0e 100644
--- a/src/ext_depends/D-YAML/source/dyaml/emitter.d
+++ b/src/ext_depends/D-YAML/source/dyaml/emitter.d
@@ -29,6 +29,7 @@ import dyaml.event;
 import dyaml.exception;
 import dyaml.linebreak;
 import dyaml.queue;
+import dyaml.scanner;
 import dyaml.style;
 import dyaml.tagdirective;
 
@@ -77,7 +78,7 @@ struct Emitter(Range, CharType) if (isOutputRange!(Range, CharType))
         Range stream_;
 
         /// Type used for upcoming emitter steps
-        alias EmitterFunction = void function(typeof(this)*) @safe;
+        alias EmitterFunction = void function(scope typeof(this)*) @safe;
 
         ///Stack of states.
         Appender!(EmitterFunction[]) states_;
@@ -732,14 +733,14 @@ struct Emitter(Range, CharType) if (isOutputRange!(Range, CharType))
             //}
             auto writer = ScalarWriter!(Range, CharType)(&this, analysis_.scalar,
                                        context_ != Context.mappingSimpleKey);
-            with(writer) final switch(style_)
+            final switch(style_)
             {
                 case ScalarStyle.invalid:      assert(false);
-                case ScalarStyle.doubleQuoted: writeDoubleQuoted(); break;
-                case ScalarStyle.singleQuoted: writeSingleQuoted(); break;
-                case ScalarStyle.folded:       writeFolded();       break;
-                case ScalarStyle.literal:      writeLiteral();      break;
-                case ScalarStyle.plain:        writePlain();        break;
+                case ScalarStyle.doubleQuoted: writer.writeDoubleQuoted(); break;
+                case ScalarStyle.singleQuoted: writer.writeSingleQuoted(); break;
+                case ScalarStyle.folded:       writer.writeFolded();       break;
+                case ScalarStyle.literal:      writer.writeLiteral();      break;
+                case ScalarStyle.plain:        writer.writePlain();        break;
             }
             analysis_.flags.isNull = true;
             style_ = ScalarStyle.invalid;
@@ -949,7 +950,7 @@ struct Emitter(Range, CharType) if (isOutputRange!(Range, CharType))
         ///Prepare anchor for output.
         static string prepareAnchor(const string anchor) @safe
             in(anchor != "",  "Anchor must not be empty")
-            in(anchor.all!(c => isAlphaNum(c) || c.among!('-', '_')), "Anchor contains invalid characters")
+            in(anchor.all!isNSAnchorName, "Anchor contains invalid characters")
         {
             return anchor;
         }
diff --git a/src/ext_depends/D-YAML/source/dyaml/escapes.d b/src/ext_depends/D-YAML/source/dyaml/escapes.d
index 32080a2..36fd744 100644
--- a/src/ext_depends/D-YAML/source/dyaml/escapes.d
+++ b/src/ext_depends/D-YAML/source/dyaml/escapes.d
@@ -11,7 +11,7 @@ package:
 
 import std.meta : AliasSeq;
 alias escapes = AliasSeq!('0', 'a', 'b', 't', '\t', 'n', 'v', 'f', 'r', 'e', ' ',
-                             '\"', '\\', 'N', '_', 'L', 'P');
+                          '/', '\"', '\\', 'N', '_', 'L', 'P');
 
 /// YAML hex codes specifying the length of the hex number.
 alias escapeHexCodeList = AliasSeq!('x', 'u', 'U');
@@ -31,6 +31,7 @@ dchar fromEscape(dchar escape) @safe pure nothrow @nogc
         case 'f':  return '\x0C';
         case 'r':  return '\x0D';
         case 'e':  return '\x1B';
+        case '/':  return '/';
         case ' ':  return '\x20';
         case '\"': return '\"';
         case '\\': return '\\';
@@ -90,3 +91,16 @@ uint escapeHexLength(dchar hexCode) @safe pure nothrow @nogc
     }
 }
 
+// Issue #302: Support optional escaping of forward slashes in string
+// for JSON compatibility
+@safe unittest
+{
+    import dyaml.loader : Loader;
+
+    const str = `{
+    "forward/slashes": "can\/be\/optionally\/escaped"
+}`;
+
+    auto node = Loader.fromString(str).load();
+    assert(node["forward/slashes"] == "can/be/optionally/escaped");
+}
diff --git a/src/ext_depends/D-YAML/source/dyaml/exception.d b/src/ext_depends/D-YAML/source/dyaml/exception.d
index 15e9c61..145e9c3 100644
--- a/src/ext_depends/D-YAML/source/dyaml/exception.d
+++ b/src/ext_depends/D-YAML/source/dyaml/exception.d
@@ -19,7 +19,7 @@ class YAMLException : Exception
 {
     /// Construct a YAMLException with specified message and position where it was thrown.
     public this(string msg, string file = __FILE__, size_t line = __LINE__)
-        @safe pure nothrow
+        @safe pure nothrow @nogc
     {
         super(msg, file, line);
     }
@@ -65,8 +65,14 @@ struct Mark
             return column_;
         }
 
+        /// Duplicate a mark
+        Mark dup () const scope @safe pure nothrow
+        {
+            return Mark(this.name_.idup, this.line_, this.column_);
+        }
+
         /// Get a string representation of the mark.
-        string toString() @safe pure nothrow const
+        string toString() const scope @safe pure nothrow
         {
             // Line/column numbers start at zero internally, make them start at 1.
             static string clamped(ushort v) @safe pure nothrow
@@ -84,23 +90,24 @@ abstract class MarkedYAMLException : YAMLException
     Mark mark;
 
     // Construct a MarkedYAMLException with specified context and problem.
-    this(string context, const Mark contextMark, string problem, const Mark problemMark,
+    this(string context, scope const Mark contextMark,
+         string problem, scope const Mark problemMark,
          string file = __FILE__, size_t line = __LINE__) @safe pure nothrow
     {
         const msg = context ~ '\n' ~
                     (contextMark != problemMark ? contextMark.toString() ~ '\n' : "") ~
                     problem ~ '\n' ~ problemMark.toString() ~ '\n';
         super(msg, file, line);
-        mark = problemMark;
+        mark = problemMark.dup;
     }
 
     // Construct a MarkedYAMLException with specified problem.
-    this(string problem, const Mark problemMark,
+    this(string problem, scope const Mark problemMark,
          string file = __FILE__, size_t line = __LINE__)
         @safe pure nothrow
     {
         super(problem ~ '\n' ~ problemMark.toString(), file, line);
-        mark = problemMark;
+        mark = problemMark.dup;
     }
 
     /// Construct a MarkedYAMLException from a struct storing constructor parameters.
diff --git a/src/ext_depends/D-YAML/source/dyaml/loader.d b/src/ext_depends/D-YAML/source/dyaml/loader.d
index 09c19db..6638dfc 100644
--- a/src/ext_depends/D-YAML/source/dyaml/loader.d
+++ b/src/ext_depends/D-YAML/source/dyaml/loader.d
@@ -82,8 +82,10 @@ struct Loader
 
         /** Construct a Loader to load YAML from a string.
          *
-         * Params:  data = String to load YAML from. The char[] version $(B will)
-         *                 overwrite its input during parsing as D:YAML reuses memory.
+         * Params:
+         *   data = String to load YAML from. The char[] version $(B will)
+         *          overwrite its input during parsing as D:YAML reuses memory.
+         *   filename = The filename to give to the Loader, defaults to `"<unknown>"`
          *
          * Returns: Loader loading YAML from given string.
          *
@@ -91,14 +93,14 @@ struct Loader
          *
          * YAMLException if data could not be read (e.g. a decoding error)
          */
-        static Loader fromString(char[] data) @safe
+        static Loader fromString(char[] data, string filename = "<unknown>") @safe
         {
-            return Loader(cast(ubyte[])data);
+            return Loader(cast(ubyte[])data, filename);
         }
         /// Ditto
-        static Loader fromString(string data) @safe
+        static Loader fromString(string data, string filename = "<unknown>") @safe
         {
-            return fromString(data.dup);
+            return fromString(data.dup, filename);
         }
         /// Load  a char[].
         @safe unittest
diff --git a/src/ext_depends/D-YAML/source/dyaml/node.d b/src/ext_depends/D-YAML/source/dyaml/node.d
index e96bcec..4c3c5eb 100644
--- a/src/ext_depends/D-YAML/source/dyaml/node.d
+++ b/src/ext_depends/D-YAML/source/dyaml/node.d
@@ -14,13 +14,17 @@ import std.array;
 import std.conv;
 import std.datetime;
 import std.exception;
+import std.format;
 import std.math;
 import std.meta : AliasSeq;
 import std.range;
 import std.string;
 import std.traits;
 import std.typecons;
-import std.variant;
+
+// FIXME: Switch back to upstream's when v2.101 is the oldest
+// supported version (recommended: after v2.111 release).
+import dyaml.stdsumtype;
 
 import dyaml.event;
 import dyaml.exception;
@@ -34,8 +38,9 @@ class NodeException : MarkedYAMLException
         //
         // Params:  msg   = Error message.
         //          start = Start position of the node.
-        this(string msg, Mark start, string file = __FILE__, size_t line = __LINE__)
-            @safe
+        this(string msg, const scope Mark start,
+             string file = __FILE__, size_t line = __LINE__)
+            @safe pure nothrow
         {
             super(msg, start, file, line);
         }
@@ -57,6 +62,9 @@ struct YAMLNull
     string toString() const pure @safe nothrow {return "null";}
 }
 
+/// Invalid YAML type, used internally by SumType
+private struct YAMLInvalid {}
+
 // Merge YAML type, used to support "tag:yaml.org,2002:merge".
 package struct YAMLMerge{}
 
@@ -79,18 +87,28 @@ private struct Pair
         }
 
         /// Equality test with another Pair.
-        bool opEquals(const ref Pair rhs) const @safe
+        bool opEquals(const ref Pair rhs) const scope @safe
         {
             return key == rhs.key && value == rhs.value;
         }
 
         // Comparison with another Pair.
-        int opCmp(ref const(Pair) rhs) const @safe
+        int opCmp(const scope ref Pair rhs) const scope @safe
         {
             const keyCmp = key.opCmp(rhs.key);
             return keyCmp != 0 ? keyCmp
                                 : value.opCmp(rhs.value);
         }
+
+    ///
+    public void toString (scope void delegate(scope const(char)[]) @safe sink)
+        const scope @safe
+    {
+        // formattedWrite does not accept `scope` parameters
+        () @trusted {
+            formattedWrite(sink, "%s: %s", this.key, this.value);
+        }();
+    }
 }
 
 enum NodeType
@@ -121,15 +139,16 @@ struct Node
 
     package:
         // YAML value type.
-        alias Value = Algebraic!(YAMLNull, YAMLMerge, bool, long, real, ubyte[], SysTime, string,
-                         Node.Pair[], Node[]);
+        alias Value = SumType!(
+            YAMLInvalid, YAMLNull, YAMLMerge,
+            bool, long, real, ubyte[], SysTime, string,
+            Node.Pair[], Node[]);
 
         // Can Value hold this type naturally?
         enum allowed(T) = isIntegral!T ||
-                       isFloatingPoint!T ||
-                       isSomeString!T ||
-                       is(Unqual!T == bool) ||
-                       Value.allowed!T;
+            isFloatingPoint!T ||
+            isSomeString!T ||
+            is(typeof({ Value i = T.init; }));
 
         // Stored value.
         Value value_;
@@ -391,19 +410,19 @@ struct Node
         }
 
         /// Is this node valid (initialized)?
-        @property bool isValid()    const @safe pure nothrow
+        @property bool isValid()    const scope @safe pure nothrow @nogc
         {
-            return value_.hasValue;
+            return value_.match!((const YAMLInvalid _) => false, _ => true);
         }
 
         /// Return tag of the node.
-        @property string tag()      const @safe nothrow
+        @property string tag()      const return scope @safe pure nothrow @nogc
         {
             return tag_;
         }
 
         /// Return the start position of the node.
-        @property Mark startMark()  const @safe pure nothrow
+        @property Mark startMark()  const return scope @safe pure nothrow @nogc
         {
             return startMark_;
         }
@@ -422,11 +441,11 @@ struct Node
          *
          * Returns: true if equal, false otherwise.
          */
-        bool opEquals(const Node rhs) const @safe
+        bool opEquals(const scope Node rhs) const scope @safe
         {
             return opCmp(rhs) == 0;
         }
-        bool opEquals(T)(const auto ref T rhs) const
+        bool opEquals(T)(const scope auto ref T rhs) const @safe
         {
             try
             {
@@ -497,10 +516,11 @@ struct Node
          * Throws:  NodeException if unable to convert to specified type, or if
          *          the value is out of range of requested type.
          */
-        inout(T) get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() inout
-            if (allowed!(Unqual!T) || hasNodeConstructor!(inout(Unqual!T)) || (!hasIndirections!(Unqual!T) && hasNodeConstructor!(Unqual!T)))
+        inout(T) get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() inout @safe return scope
         {
-            if(isType!(Unqual!T)){return getValue!T;}
+            static assert (allowed!(Unqual!T) ||
+                           hasNodeConstructor!(inout(Unqual!T)) ||
+                           (!hasIndirections!(Unqual!T) && hasNodeConstructor!(Unqual!T)));
 
             static if(!allowed!(Unqual!T))
             {
@@ -530,6 +550,8 @@ struct Node
                     static assert(0, "Unhandled user type");
                 }
             } else {
+                static if (canBeType!T)
+                    if (isType!(Unqual!T)) { return getValue!T; }
 
                 // If we're getting from a mapping and we're not getting Node.Pair[],
                 // we're getting the default value.
@@ -549,9 +571,9 @@ struct Node
                         // Try to convert to string.
                         try
                         {
-                            return coerceValue!T();
+                            return coerceValue!T().dup;
                         }
-                        catch(VariantException e)
+                        catch (MatchException e)
                         {
                             throw new NodeException("Unable to convert node value to string", startMark_);
                         }
@@ -646,9 +668,11 @@ struct Node
                     this.z = z;
                 }
 
-                this(Node node) @safe
+                this(scope const Node node) @safe
                 {
-                    auto parts = node.as!string().split(":");
+                    // `std.array.split` is not marked as taking a `scope` range,
+                    // but we don't escape a reference.
+                    scope parts = () @trusted { return node.as!string().split(":"); }();
                     x = parts[0].to!int;
                     y = parts[1].to!int;
                     z = parts[2].to!int;
@@ -770,9 +794,11 @@ struct Node
                     this.z = z;
                 }
 
-                this(Node node) @safe inout
+                this(scope const Node node) @safe inout
                 {
-                    auto parts = node.as!string().split(":");
+                    // `std.array.split` is not marked as taking a `scope` range,
+                    // but we don't escape a reference.
+                    scope parts = () @trusted { return node.as!string().split(":"); }();
                     x = parts[0].to!int;
                     y = parts[1].to!int;
                     z = parts[2].to!int;
@@ -921,7 +947,7 @@ struct Node
          *          non-integral index is used with a sequence or the node is
          *          not a collection.
          */
-        ref inout(Node) opIndex(T)(T index) inout @safe
+        ref inout(Node) opIndex(T)(T index) inout return scope @safe
         {
             final switch (nodeID)
             {
@@ -1362,7 +1388,7 @@ struct Node
 
             string[int] test;
             foreach (pair; n.mapping)
-                test[pair.key.as!int] = pair.value.as!string;
+                test[pair.key.as!int] = pair.value.as!string.idup;
 
             assert(test[1] == "foo");
             assert(test[2] == "bar");
@@ -1665,7 +1691,7 @@ struct Node
                                Pair(k3, Node(cast(real)1.0)),
                                Pair(k4, Node("yarly"))]);
 
-            foreach(string key, Node value; nmap2)
+            foreach(scope string key, scope Node value; nmap2)
             {
                 switch(key)
                 {
@@ -1957,12 +1983,16 @@ struct Node
         }
 
         /// Compare with another _node.
-        int opCmp(const ref Node rhs) const @safe
-        {
-            // Compare tags - if equal or both null, we need to compare further.
-            const tagCmp = (tag_ is null) ? (rhs.tag_ is null) ? 0 : -1
-                                       : (rhs.tag_ is null) ? 1 : std.algorithm.comparison.cmp(tag_, rhs.tag_);
-            if(tagCmp != 0){return tagCmp;}
+        int opCmp(const scope ref Node rhs) const scope @safe
+        {
+            const bool hasNullTag = this.tag_ is null;
+            // Only one of them is null: we can order nodes
+            if ((hasNullTag) ^ (rhs.tag is null))
+                return hasNullTag ? -1 : 1;
+            // Either both `null` or both have a value
+            if (!hasNullTag)
+                if (int result = std.algorithm.comparison.cmp(tag_, rhs.tag_))
+                    return result;
 
             static int cmp(T1, T2)(T1 a, T2 b)
             {
@@ -1972,15 +2002,14 @@ struct Node
             }
 
             // Compare validity: if both valid, we have to compare further.
-            const v1 = isValid;
-            const v2 = rhs.isValid;
-            if(!v1){return v2 ? -1 : 0;}
-            if(!v2){return 1;}
-
-            const typeCmp = cmp(type, rhs.type);
-            if(typeCmp != 0){return typeCmp;}
+            if (!this.isValid())
+                return rhs.isValid() ? -1 : 0;
+            if (!rhs.isValid())
+                return 1;
+            if (const typeCmp = cmp(type, rhs.type))
+                return typeCmp;
 
-            static int compareCollections(T)(const ref Node lhs, const ref Node rhs)
+            static int compareCollections(T)(const scope ref Node lhs, const scope ref Node rhs)
             {
                 const c1 = lhs.getValue!T;
                 const c2 = rhs.getValue!T;
@@ -2070,7 +2099,7 @@ struct Node
         // Compute hash of the node.
         hash_t toHash() nothrow const @trusted
         {
-            const valueHash = value_.toHash();
+            const valueHash = value_.match!(v => hashOf(v));
 
             return tag_ is null ? valueHash : tag_.hashOf(valueHash);
         }
@@ -2081,57 +2110,25 @@ struct Node
         }
 
         /// Get type of the node value.
-        @property NodeType type() const @safe nothrow
-        {
-            if (value_.type is typeid(bool))
-            {
-                return NodeType.boolean;
-            }
-            else if (value_.type is typeid(long))
-            {
-                return NodeType.integer;
-            }
-            else if (value_.type is typeid(Node[]))
-            {
-                return NodeType.sequence;
-            }
-            else if (value_.type is typeid(ubyte[]))
-            {
-                return NodeType.binary;
-            }
-            else if (value_.type is typeid(string))
-            {
-                return NodeType.string;
-            }
-            else if (value_.type is typeid(Node.Pair[]))
-            {
-                return NodeType.mapping;
-            }
-            else if (value_.type is typeid(SysTime))
-            {
-                return NodeType.timestamp;
-            }
-            else if (value_.type is typeid(YAMLNull))
-            {
-                return NodeType.null_;
-            }
-            else if (value_.type is typeid(YAMLMerge))
-            {
-                return NodeType.merge;
-            }
-            else if (value_.type is typeid(real))
-            {
-                return NodeType.decimal;
-            }
-            else if (!value_.hasValue)
-            {
-                return NodeType.invalid;
-            }
-            else assert(0, text(value_.type));
+        @property NodeType type() const scope @safe pure nothrow @nogc
+        {
+            return this.value_.match!(
+                (const bool _)        => NodeType.boolean,
+                (const long _)        => NodeType.integer,
+                (const Node[] _)      => NodeType.sequence,
+                (const ubyte[] _)     => NodeType.binary,
+                (const string _)      => NodeType.string,
+                (const Node.Pair[] _) => NodeType.mapping,
+                (const SysTime _)     => NodeType.timestamp,
+                (const YAMLNull _)    => NodeType.null_,
+                (const YAMLMerge _)   => NodeType.merge,
+                (const real _)        => NodeType.decimal,
+                (const YAMLInvalid _) => NodeType.invalid,
+            );
         }
 
         /// Get the kind of node this is.
-        @property NodeID nodeID() const @safe nothrow
+        @property NodeID nodeID() const scope @safe pure nothrow @nogc
         {
             final switch (type)
             {
@@ -2159,7 +2156,7 @@ struct Node
         // Params:  level = Level of the node in the tree.
         //
         // Returns: String representing the node tree.
-        @property string debugString(uint level = 0) const @safe
+        @property string debugString(uint level = 0) const scope @safe
         {
             string indent;
             foreach(i; 0 .. level){indent ~= " ";}
@@ -2192,7 +2189,7 @@ struct Node
 
 
     public:
-        @property string nodeTypeString() const @safe nothrow
+        @property string nodeTypeString() const scope @safe pure nothrow @nogc
         {
             final switch (nodeID)
             {
@@ -2325,9 +2322,16 @@ struct Node
         // This only works for default YAML types, not for user defined types.
         @property bool isType(T)() const
         {
-            return value_.type is typeid(Unqual!T);
+            return value_.match!(
+                (const T _) => true,
+                _    => false,
+            );
         }
 
+        /// Check at compile time if a type is stored natively
+        enum canBeType (T) = is(typeof({ value_.match!((const T _) => true, _ => false); }));
+
+
         // Implementation of contains() and containsKey().
         bool contains_(T, Flag!"key" key, string func)(T rhs) const
         {
@@ -2403,7 +2407,8 @@ struct Node
 
         // Get index of pair with key (or value, if key is false) matching index.
         // Cannot be inferred @safe due to https://issues.dlang.org/show_bug.cgi?id=16528
-        sizediff_t findPair(T, Flag!"key" key = Yes.key)(const ref T index) const @safe
+        sizediff_t findPair(T, Flag!"key" key = Yes.key)(const scope ref T index)
+            const scope @safe
         {
             const pairs = getValue!(Pair[])();
             const(Node)* node;
@@ -2427,7 +2432,7 @@ struct Node
         }
 
         // Check if index is integral and in range.
-        void checkSequenceIndex(T)(T index) const
+        void checkSequenceIndex(T)(T index) const scope @safe
         {
             assert(nodeID == NodeID.sequence,
                    "checkSequenceIndex() called on a " ~ nodeTypeString ~ " node");
@@ -2444,14 +2449,42 @@ struct Node
             }
         }
         // Safe wrapper for getting a value out of the variant.
-        inout(T) getValue(T)() @trusted inout
+        inout(T) getValue(T)() @safe return scope inout
         {
-            return value_.get!T;
+            alias RType = typeof(return);
+            return value_.tryMatch!((RType r) => r);
         }
         // Safe wrapper for coercing a value out of the variant.
-        inout(T) coerceValue(T)() @trusted inout
-        {
-            return (cast(Value)value_).coerce!T;
+        inout(T) coerceValue(T)() @trusted scope return inout
+        {
+            alias RType = typeof(return);
+            static if (is(typeof({ RType rt = T.init; T t = RType.init; })))
+                alias TType = T;
+            else // `inout` matters (indirection)
+                alias TType = RType;
+
+            // `inout(Node[]).to!string` apparently is not safe:
+            // struct SumTypeBug {
+            //     import std.conv;
+            //     Node[] data;
+            //
+            //     string bug () inout @safe
+            //     {
+            //         return this.data.to!string;
+            //     }
+            // }
+            // Doesn't compile with DMD v2.100.0
+            return this.value_.tryMatch!(
+                (inout bool v) @safe      => v.to!TType,
+                (inout long v) @safe      => v.to!TType,
+                (inout Node[] v) @trusted => v.to!TType,
+                (inout ubyte[] v) @safe   => v.to!TType,
+                (inout string v) @safe    => v.to!TType,
+                (inout Node.Pair[] v) @trusted => v.to!TType,
+                (inout SysTime v) @trusted   => v.to!TType,
+                (inout real v) @safe      => v.to!TType,
+                (inout YAMLNull v) @safe  => null.to!TType,
+            );
         }
         // Safe wrapper for setting a value for the variant.
         void setValue(T)(T value) @trusted
@@ -2469,6 +2502,25 @@ struct Node
                 value_ = tmpNode.value_;
             }
         }
+
+    ///
+    public void toString (DGT) (scope DGT sink)
+        const scope @safe
+    {
+        this.value_.match!(
+            (const bool v)        => formattedWrite(sink, v ? "true" : "false"),
+            (const long v)        => formattedWrite(sink, "%s", v),
+            (const Node[] v)      => formattedWrite(sink, "[%(%s, %)]", v),
+            (const ubyte[] v)     => formattedWrite(sink, "%s", v),
+            (const string v)      => formattedWrite(sink, `"%s"`, v),
+            (const Node.Pair[] v) => formattedWrite(sink, "{%(%s, %)}", v),
+            (const SysTime v)     => formattedWrite(sink, "%s", v),
+            (const YAMLNull v)    => formattedWrite(sink, "%s", v),
+            (const YAMLMerge v)   => formattedWrite(sink, "%s", v),
+            (const real v)        => formattedWrite(sink, "%s", v),
+            (const YAMLInvalid v) => formattedWrite(sink, "%s", v),
+        );
+    }
 }
 
 package:
@@ -2481,7 +2533,10 @@ package:
 //          toMerge = Pairs to merge.
 void merge(ref Appender!(Node.Pair[]) pairs, Node.Pair[] toMerge) @safe
 {
-    bool eq(ref Node.Pair a, ref Node.Pair b){return a.key == b.key;}
+    bool eq(ref Node.Pair a, ref Node.Pair b) @safe
+    {
+        return a.key == b.key;
+    }
 
     foreach(ref pair; toMerge) if(!canFind!eq(pairs.data, pair))
     {
@@ -2528,7 +2583,7 @@ enum castableToNode(T) = (is(T == struct) || is(T == class)) && is(typeof(T.opCa
         {
             foreach(value; node["bars"].sequence)
             {
-                bars ~= value.as!string;
+                bars ~= value.as!string.idup;
             }
         }
     }
diff --git a/src/ext_depends/D-YAML/source/dyaml/resolver.d b/src/ext_depends/D-YAML/source/dyaml/resolver.d
index f57cbbe..16d8419 100644
--- a/src/ext_depends/D-YAML/source/dyaml/resolver.d
+++ b/src/ext_depends/D-YAML/source/dyaml/resolver.d
@@ -163,7 +163,7 @@ struct Resolver
          *
          * Returns: Resolved tag.
          */
-        string resolve(const NodeID kind, const string tag, const string value,
+        string resolve(const NodeID kind, const string tag, scope string value,
                     const bool implicit) @safe
         {
             import std.array : empty, front;
@@ -189,7 +189,13 @@ struct Resolver
                     //If regexp matches, return tag.
                     foreach(resolver; resolvers)
                     {
-                        if(!(match(value, resolver[1]).empty))
+                        // source/dyaml/resolver.d(192,35): Error: scope variable `__tmpfordtorXXX`
+                        // assigned to non-scope parameter `this` calling
+                        // `std.regex.RegexMatch!string.RegexMatch.~this`
+                        bool isEmpty = () @trusted {
+                            return match(value, resolver[1]).empty;
+                        }();
+                        if(!isEmpty)
                         {
                             return resolver[0];
                         }
diff --git a/src/ext_depends/D-YAML/source/dyaml/scanner.d b/src/ext_depends/D-YAML/source/dyaml/scanner.d
index 3f0f394..77c3e38 100644
--- a/src/ext_depends/D-YAML/source/dyaml/scanner.d
+++ b/src/ext_depends/D-YAML/source/dyaml/scanner.d
@@ -72,6 +72,8 @@ alias isBChar = among!('\n', '\r', '\u0085', '\u2028', '\u2029');
 
 alias isFlowScalarBreakSpace = among!(' ', '\t', '\0', '\n', '\r', '\u0085', '\u2028', '\u2029', '\'', '"', '\\');
 
+alias isNSAnchorName = c => !c.isWhiteSpace && !c.among!('[', ']', '{', '}', ',', '\uFEFF');
+
 /// Marked exception thrown at scanner errors.
 ///
 /// See_Also: MarkedYAMLException
@@ -763,6 +765,25 @@ struct Scanner
             reader_.sliceBuilder.write(reader_.get(length));
         }
 
+        /// Scan a string.
+        ///
+        /// Assumes that the caller is building a slice in Reader, and puts the scanned
+        /// characters into that slice.
+        void scanAnchorAliasToSlice(const Mark startMark) @safe
+        {
+            size_t length;
+            dchar c = reader_.peek();
+            while (c.isNSAnchorName)
+            {
+                c = reader_.peek(++length);
+            }
+
+            enforce(length > 0, new ScannerException("While scanning an anchor or alias",
+                startMark, expected("a printable character besides '[', ']', '{', '}' and ','", c), reader_.mark));
+
+            reader_.sliceBuilder.write(reader_.get(length));
+        }
+
         /// Scan and throw away all characters until next line break.
         void scanToNextBreak() @safe
         {
@@ -988,20 +1009,14 @@ struct Scanner
         Token scanAnchor(const TokenID id) @safe
         {
             const startMark = reader_.mark;
-            const dchar i = reader_.get();
+            reader_.forward(); // The */& character was only peeked, so we drop it now
 
             reader_.sliceBuilder.begin();
-            if(i == '*') { scanAlphaNumericToSlice!"an alias"(startMark); }
-            else         { scanAlphaNumericToSlice!"an anchor"(startMark); }
+            scanAnchorAliasToSlice(startMark);
             // On error, value is discarded as we return immediately
             char[] value = reader_.sliceBuilder.finish();
 
-            enum anchorCtx = "While scanning an anchor";
-            enum aliasCtx  = "While scanning an alias";
-            enforce(reader_.peek().isWhiteSpace ||
-                reader_.peekByte().among!('?', ':', ',', ']', '}', '%', '@'),
-                new ScannerException(i == '*' ? aliasCtx : anchorCtx, startMark,
-                    expected("alphanumeric, '-' or '_'", reader_.peek()), reader_.mark));
+            assert(!reader_.peek().isNSAnchorName, "Anchor/alias name not fully scanned");
 
             if(id == TokenID.alias_)
             {
@@ -1212,7 +1227,10 @@ struct Scanner
                 // is not Strip)
                 else
                 {
-                    reader_.sliceBuilder.write(lineBreak);
+                    if (lineBreak != '\0')
+                    {
+                        reader_.sliceBuilder.write(lineBreak);
+                    }
                 }
             }
 
@@ -1792,3 +1810,17 @@ struct Scanner
             return '\0';
         }
 }
+
+// Issue 309 - https://github.com/dlang-community/D-YAML/issues/309
+@safe unittest
+{
+    enum str = q"EOS
+exp: |
+  foobar
+EOS".chomp;
+
+    auto r = new Reader(cast(ubyte[])str.dup);
+    auto s = Scanner(r);
+    auto elems = s.map!"a.value".filter!"a.length > 0".array;
+    assert(elems[1] == "foobar");
+}
diff --git a/src/ext_depends/D-YAML/source/dyaml/serializer.d b/src/ext_depends/D-YAML/source/dyaml/serializer.d
index 4100cf3..cbaef63 100644
--- a/src/ext_depends/D-YAML/source/dyaml/serializer.d
+++ b/src/ext_depends/D-YAML/source/dyaml/serializer.d
@@ -236,7 +236,7 @@ struct Serializer
                     const bool isDetected = node.tag_ == detectedTag;
 
                     emitter.emit(scalarEvent(Mark(), Mark(), aliased, node.tag_,
-                                  isDetected, value, node.scalarStyle));
+                                  isDetected, value.idup, node.scalarStyle));
                     return;
                 case NodeID.invalid:
                     assert(0);
diff --git a/src/ext_depends/D-YAML/source/dyaml/stdsumtype.d b/src/ext_depends/D-YAML/source/dyaml/stdsumtype.d
new file mode 100644
index 0000000..3dac4dd
--- /dev/null
+++ b/src/ext_depends/D-YAML/source/dyaml/stdsumtype.d
@@ -0,0 +1,2627 @@
+/++
+ This module was copied from Phobos at commit 87c6e7e35 (2022-07-06).
+ This is necessary to include https://github.com/dlang/phobos/pull/8501
+ which is a fix needed for DIP1000 compatibility. A couple minor changes
+ where also required to deal with `package(std)` imports.
+
+[SumType] is a generic discriminated union implementation that uses
+design-by-introspection to generate safe and efficient code. Its features
+include:
+
+* [Pattern matching.][match]
+* Support for self-referential types.
+* Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are
+    inferred whenever possible).
+* A type-safe and memory-safe API compatible with DIP 1000 (`scope`).
+* No dependency on runtime type information (`TypeInfo`).
+* Compatibility with BetterC.
+
+License: Boost License 1.0
+Authors: Paul Backus
+Source: $(PHOBOSSRC std/sumtype.d)
++/
+module dyaml.stdsumtype;
+
+/// $(DIVID basic-usage,$(H3 Basic usage))
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.math.operations : isClose;
+
+    struct Fahrenheit { double degrees; }
+    struct Celsius { double degrees; }
+    struct Kelvin { double degrees; }
+
+    alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);
+
+    // Construct from any of the member types.
+    Temperature t1 = Fahrenheit(98.6);
+    Temperature t2 = Celsius(100);
+    Temperature t3 = Kelvin(273);
+
+    // Use pattern matching to access the value.
+    Fahrenheit toFahrenheit(Temperature t)
+    {
+        return Fahrenheit(
+            t.match!(
+                (Fahrenheit f) => f.degrees,
+                (Celsius c) => c.degrees * 9.0/5 + 32,
+                (Kelvin k) => k.degrees * 9.0/5 - 459.4
+            )
+        );
+    }
+
+    assert(toFahrenheit(t1).degrees.isClose(98.6));
+    assert(toFahrenheit(t2).degrees.isClose(212));
+    assert(toFahrenheit(t3).degrees.isClose(32));
+
+    // Use ref to modify the value in place.
+    void freeze(ref Temperature t)
+    {
+        t.match!(
+            (ref Fahrenheit f) => f.degrees = 32,
+            (ref Celsius c) => c.degrees = 0,
+            (ref Kelvin k) => k.degrees = 273
+        );
+    }
+
+    freeze(t1);
+    assert(toFahrenheit(t1).degrees.isClose(32));
+
+    // Use a catch-all handler to give a default result.
+    bool isFahrenheit(Temperature t)
+    {
+        return t.match!(
+            (Fahrenheit f) => true,
+            _ => false
+        );
+    }
+
+    assert(isFahrenheit(t1));
+    assert(!isFahrenheit(t2));
+    assert(!isFahrenheit(t3));
+}
+
+/** $(DIVID introspection-based-matching, $(H3 Introspection-based matching))
+ *
+ * In the `length` and `horiz` functions below, the handlers for `match` do not
+ * specify the types of their arguments. Instead, matching is done based on how
+ * the argument is used in the body of the handler: any type with `x` and `y`
+ * properties will be matched by the `rect` handlers, and any type with `r` and
+ * `theta` properties will be matched by the `polar` handlers.
+ */
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.math.operations : isClose;
+    import std.math.trigonometry : cos;
+    import std.math.constants : PI;
+    import std.math.algebraic : sqrt;
+
+    struct Rectangular { double x, y; }
+    struct Polar { double r, theta; }
+    alias Vector = SumType!(Rectangular, Polar);
+
+    double length(Vector v)
+    {
+        return v.match!(
+            rect => sqrt(rect.x^^2 + rect.y^^2),
+            polar => polar.r
+        );
+    }
+
+    double horiz(Vector v)
+    {
+        return v.match!(
+            rect => rect.x,
+            polar => polar.r * cos(polar.theta)
+        );
+    }
+
+    Vector u = Rectangular(1, 1);
+    Vector v = Polar(1, PI/4);
+
+    assert(length(u).isClose(sqrt(2.0)));
+    assert(length(v).isClose(1));
+    assert(horiz(u).isClose(1));
+    assert(horiz(v).isClose(sqrt(0.5)));
+}
+
+/** $(DIVID arithmetic-expression-evaluator, $(H3 Arithmetic expression evaluator))
+ *
+ * This example makes use of the special placeholder type `This` to define a
+ * [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an
+ * [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for
+ * representing simple arithmetic expressions.
+ */
+version (D_BetterC) {} else
+@system unittest
+{
+    import std.functional : partial;
+    import std.traits : EnumMembers;
+    import std.typecons : Tuple;
+
+    enum Op : string
+    {
+        Plus  = "+",
+        Minus = "-",
+        Times = "*",
+        Div   = "/"
+    }
+
+    // An expression is either
+    //  - a number,
+    //  - a variable, or
+    //  - a binary operation combining two sub-expressions.
+    alias Expr = SumType!(
+        double,
+        string,
+        Tuple!(Op, "op", This*, "lhs", This*, "rhs")
+    );
+
+    // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"),
+    // the Tuple type above with Expr substituted for This.
+    alias BinOp = Expr.Types[2];
+
+    // Factory function for number expressions
+    Expr* num(double value)
+    {
+        return new Expr(value);
+    }
+
+    // Factory function for variable expressions
+    Expr* var(string name)
+    {
+        return new Expr(name);
+    }
+
+    // Factory function for binary operation expressions
+    Expr* binOp(Op op, Expr* lhs, Expr* rhs)
+    {
+        return new Expr(BinOp(op, lhs, rhs));
+    }
+
+    // Convenience wrappers for creating BinOp expressions
+    alias sum  = partial!(binOp, Op.Plus);
+    alias diff = partial!(binOp, Op.Minus);
+    alias prod = partial!(binOp, Op.Times);
+    alias quot = partial!(binOp, Op.Div);
+
+    // Evaluate expr, looking up variables in env
+    double eval(Expr expr, double[string] env)
+    {
+        return expr.match!(
+            (double num) => num,
+            (string var) => env[var],
+            (BinOp bop)
+            {
+                double lhs = eval(*bop.lhs, env);
+                double rhs = eval(*bop.rhs, env);
+                final switch (bop.op)
+                {
+                    static foreach (op; EnumMembers!Op)
+                    {
+                        case op:
+                            return mixin("lhs" ~ op ~ "rhs");
+                    }
+                }
+            }
+        );
+    }
+
+    // Return a "pretty-printed" representation of expr
+    string pprint(Expr expr)
+    {
+        import std.format : format;
+
+        return expr.match!(
+            (double num) => "%g".format(num),
+            (string var) => var,
+            (BinOp bop) => "(%s %s %s)".format(
+                pprint(*bop.lhs),
+                cast(string) bop.op,
+                pprint(*bop.rhs)
+            )
+        );
+    }
+
+    Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
+    double[string] myEnv = ["a":3, "b":4, "c":7];
+
+    assert(eval(*myExpr, myEnv) == 11);
+    assert(pprint(*myExpr) == "(a + (2 * b))");
+}
+
+import std.format.spec : FormatSpec, singleSpec;
+import std.meta : AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap;
+import std.meta : NoDuplicates;
+import std.meta : anySatisfy, allSatisfy;
+import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor;
+import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable;
+import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf;
+
+// FIXME: std.sumtype : `std.traits : DeducedParameterType` and `std.conv : toCtString`
+// are `package(std)` but trivial, hence copied below
+import std.traits : CommonType, /*DeducatedParameterType*/ Unqual;
+private template DeducedParameterType(T)
+{
+    static if (is(T == U*, U) || is(T == U[], U))
+        alias DeducedParameterType = Unqual!T;
+    else
+        alias DeducedParameterType = T;
+}
+
+import std.typecons : ReplaceTypeUnless;
+import std.typecons : Flag;
+//import std.conv : toCtString;
+private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length];
+
+/// Placeholder used to refer to the enclosing [SumType].
+struct This {}
+
+// True if a variable of type T can appear on the lhs of an assignment
+private enum isAssignableTo(T) =
+    isAssignable!T || (!isCopyable!T && isRvalueAssignable!T);
+
+// toHash is required by the language spec to be nothrow and @safe
+private enum isHashable(T) = __traits(compiles,
+    () nothrow @safe { hashOf(T.init); }
+);
+
+private enum hasPostblit(T) = __traits(hasPostblit, T);
+
+private enum isInout(T) = is(T == inout);
+
+/**
+ * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a
+ * single value from any of a specified set of types.
+ *
+ * The value in a `SumType` can be operated on using [pattern matching][match].
+ *
+ * To avoid ambiguity, duplicate types are not allowed (but see the
+ * ["basic usage" example](#basic-usage) for a workaround).
+ *
+ * The special type `This` can be used as a placeholder to create
+ * self-referential types, just like with `Algebraic`. See the
+ * ["Arithmetic expression evaluator" example](#arithmetic-expression-evaluator) for
+ * usage.
+ *
+ * A `SumType` is initialized by default to hold the `.init` value of its
+ * first member type, just like a regular union. The version identifier
+ * `SumTypeNoDefaultCtor` can be used to disable this behavior.
+ *
+ * See_Also: $(REF Algebraic, std,variant)
+ */
+struct SumType(Types...)
+if (is(NoDuplicates!Types == Types) && Types.length > 0)
+{
+    /// The types a `SumType` can hold.
+    alias Types = AliasSeq!(
+        ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType)
+    );
+
+private:
+
+    enum bool canHoldTag(T) = Types.length <= T.max;
+    alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong);
+
+    alias Tag = Filter!(canHoldTag, unsignedInts)[0];
+
+    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)
+        {
+            mixin("T ", memberName!T, ";");
+        }
+    }
+
+    Storage storage;
+    Tag tag;
+
+    /* Accesses the value stored in a SumType.
+     *
+     * 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.
+     *
+     * 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)
+    {
+        enum tid = IndexOf!(T, Types);
+        assert(tag == tid,
+            "This `" ~ SumType.stringof ~
+            "` does not contain a(n) `" ~ T.stringof ~ "`"
+        );
+        return __traits(getMember, storage, Storage.memberName!T);
+    }
+
+public:
+
+    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399
+    version (StdDdoc)
+    {
+        // Dummy type to stand in for loop variable
+        private struct T;
+
+        /// Constructs a `SumType` holding a specific value.
+        this(T value);
+
+        /// ditto
+        this(const(T) value) const;
+
+        /// ditto
+        this(immutable(T) value) immutable;
+
+        /// ditto
+        this(Value)(Value value) inout
+        if (is(Value == DeducedParameterType!(inout(T))));
+    }
+
+    static foreach (tid, T; Types)
+    {
+        /// Constructs a `SumType` holding a specific value.
+        this(T value)
+        {
+            import core.lifetime : forward;
+
+            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;
+            }
+            else
+            {
+                __traits(getMember, storage, Storage.memberName!T) = forward!value;
+            }
+
+            tag = tid;
+        }
+
+        static if (isCopyable!(const(T)))
+        {
+            static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid)
+            {
+                /// ditto
+                this(const(T) value) const
+                {
+                    __traits(getMember, storage, Storage.memberName!T) = value;
+                    tag = tid;
+                }
+            }
+        }
+        else
+        {
+            @disable this(const(T) value) const;
+        }
+
+        static if (isCopyable!(immutable(T)))
+        {
+            static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid)
+            {
+                /// ditto
+                this(immutable(T) value) immutable
+                {
+                    __traits(getMember, storage, Storage.memberName!T) = value;
+                    tag = tid;
+                }
+            }
+        }
+        else
+        {
+            @disable this(immutable(T) value) immutable;
+        }
+
+        static if (isCopyable!(inout(T)))
+        {
+            static if (IndexOf!(inout(T), Map!(InoutOf, Types)) == tid)
+            {
+                /// ditto
+                this(Value)(Value value) inout
+                if (is(Value == DeducedParameterType!(inout(T))))
+                {
+                    __traits(getMember, storage, Storage.memberName!T) = value;
+                    tag = tid;
+                }
+            }
+        }
+        else
+        {
+            @disable this(Value)(Value value) inout
+            if (is(Value == DeducedParameterType!(inout(T))));
+        }
+    }
+
+    static if (anySatisfy!(hasElaborateCopyConstructor, Types))
+    {
+        static if
+        (
+            allSatisfy!(isCopyable, Map!(InoutOf, Types))
+            && !anySatisfy!(hasPostblit, Map!(InoutOf, Types))
+            && allSatisfy!(isInout, Map!(InoutOf, Types))
+        )
+        {
+            /// Constructs a `SumType` that's a copy of another `SumType`.
+            this(ref inout(SumType) other) inout
+            {
+                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",
+                    " };");
+
+                    return newStorage;
+                });
+
+                tag = other.tag;
+            }
+        }
+        else
+        {
+            static if (allSatisfy!(isCopyable, Types))
+            {
+                /// ditto
+                this(ref SumType other)
+                {
+                    storage = other.match!((ref value) {
+                        alias T = typeof(value);
+
+                        mixin("Storage newStorage = { ",
+                            Storage.memberName!T, ": value",
+                        " };");
+
+                        return newStorage;
+                    });
+
+                    tag = other.tag;
+                }
+            }
+            else
+            {
+                @disable this(ref SumType other);
+            }
+
+            static if (allSatisfy!(isCopyable, Map!(ConstOf, Types)))
+            {
+                /// ditto
+                this(ref const(SumType) other) const
+                {
+                    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",
+                        " };");
+
+                        return newStorage;
+                    });
+
+                    tag = other.tag;
+                }
+            }
+            else
+            {
+                @disable this(ref const(SumType) other) const;
+            }
+
+            static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types)))
+            {
+                /// ditto
+                this(ref immutable(SumType) other) immutable
+                {
+                    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",
+                        " };");
+
+                        return newStorage;
+                    });
+
+                    tag = other.tag;
+                }
+            }
+            else
+            {
+                @disable this(ref immutable(SumType) other) immutable;
+            }
+        }
+    }
+
+    version (SumTypeNoDefaultCtor)
+    {
+        @disable this();
+    }
+
+    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399
+    version (StdDdoc)
+    {
+        // Dummy type to stand in for loop variable
+        private struct T;
+
+        /**
+         * Assigns a value to a `SumType`.
+         *
+         * If any of the `SumType`'s members other than the one being assigned
+         * to contain pointers or references, it is possible for the assignment
+         * to cause memory corruption (see the
+         * ["Memory corruption" example](#memory-corruption) below for an
+         * illustration of how). Therefore, such assignments are considered
+         * `@system`.
+         *
+         * An individual assignment can be `@trusted` if the caller can
+         * guarantee that there are no outstanding references to any `SumType`
+         * members that contain pointers or references at the time the
+         * assignment occurs.
+         *
+         * Examples:
+         *
+         * $(DIVID memory-corruption, $(H3 Memory corruption))
+         *
+         * This example shows how assignment to a `SumType` can be used to
+         * cause memory corruption in `@system` code. In `@safe` code, the
+         * assignment `s = 123` would not be allowed.
+         *
+         * ---
+         * SumType!(int*, int) s = new int;
+         * s.tryMatch!(
+         *     (ref int* p) {
+         *         s = 123; // overwrites `p`
+         *         return *p; // undefined behavior
+         *     }
+         * );
+         * ---
+         */
+        ref SumType opAssign(T rhs);
+    }
+
+    static foreach (tid, T; Types)
+    {
+        static if (isAssignableTo!T)
+        {
+            /**
+             * Assigns a value to a `SumType`.
+             *
+             * If any of the `SumType`'s members other than the one being assigned
+             * to contain pointers or references, it is possible for the assignment
+             * to cause memory corruption (see the
+             * ["Memory corruption" example](#memory-corruption) below for an
+             * illustration of how). Therefore, such assignments are considered
+             * `@system`.
+             *
+             * An individual assignment can be `@trusted` if the caller can
+             * guarantee that there are no outstanding references to any `SumType`
+             * members that contain pointers or references at the time the
+             * assignment occurs.
+             *
+             * Examples:
+             *
+             * $(DIVID memory-corruption, $(H3 Memory corruption))
+             *
+             * This example shows how assignment to a `SumType` can be used to
+             * cause memory corruption in `@system` code. In `@safe` code, the
+             * assignment `s = 123` would not be allowed.
+             *
+             * ---
+             * SumType!(int*, int) s = new int;
+             * s.tryMatch!(
+             *     (ref int* p) {
+             *         s = 123; // overwrites `p`
+             *         return *p; // undefined behavior
+             *     }
+             * );
+             * ---
+             */
+            ref SumType opAssign(T rhs)
+            {
+                import core.lifetime : forward;
+                import std.traits : hasIndirections, hasNested;
+                import std.meta : AliasSeq, Or = templateOr;
+
+                alias OtherTypes =
+                    AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]);
+                enum unsafeToOverwrite =
+                    anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes);
+
+                static if (unsafeToOverwrite)
+                {
+                    cast(void) () @system {}();
+                }
+
+                this.match!destroyIfOwner;
+
+                static if (isCopyable!T)
+                {
+                    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
+                    mixin("Storage newStorage = { ",
+                        Storage.memberName!T, ": __ctfe ? rhs : forward!rhs",
+                    " };");
+                }
+                else
+                {
+                    mixin("Storage newStorage = { ",
+                        Storage.memberName!T, ": forward!rhs",
+                    " };");
+                }
+
+                storage = newStorage;
+                tag = tid;
+
+                return this;
+            }
+        }
+    }
+
+    static if (allSatisfy!(isAssignableTo, Types))
+    {
+        static if (allSatisfy!(isCopyable, Types))
+        {
+            /**
+             * Copies the value from another `SumType` into this one.
+             *
+             * See the value-assignment overload for details on `@safe`ty.
+             *
+             * Copy assignment is `@disable`d if any of `Types` is non-copyable.
+             */
+            ref SumType opAssign(ref SumType rhs)
+            {
+                rhs.match!((ref value) { this = value; });
+                return this;
+            }
+        }
+        else
+        {
+            @disable ref SumType opAssign(ref SumType rhs);
+        }
+
+        /**
+         * Moves the value from another `SumType` into this one.
+         *
+         * See the value-assignment overload for details on `@safe`ty.
+         */
+        ref SumType opAssign(SumType rhs)
+        {
+            import core.lifetime : move;
+
+            rhs.match!((ref value) {
+                static if (isCopyable!(typeof(value)))
+                {
+                    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
+                    this = __ctfe ? value : move(value);
+                }
+                else
+                {
+                    this = move(value);
+                }
+            });
+            return this;
+        }
+    }
+
+    /**
+     * Compares two `SumType`s for equality.
+     *
+     * Two `SumType`s are equal if they are the same kind of `SumType`, they
+     * contain values of the same type, and those values are equal.
+     */
+    bool opEquals(this This, Rhs)(auto ref Rhs rhs)
+    if (!is(CommonType!(This, Rhs) == void))
+    {
+        static if (is(This == Rhs))
+        {
+            return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) {
+                static if (is(typeof(value) == typeof(rhsValue)))
+                {
+                    return value == rhsValue;
+                }
+                else
+                {
+                    return false;
+                }
+            });
+        }
+        else
+        {
+            alias CommonSumType = CommonType!(This, Rhs);
+            return cast(CommonSumType) this == cast(CommonSumType) rhs;
+        }
+    }
+
+    // Workaround for https://issues.dlang.org/show_bug.cgi?id=19407
+    static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types)))
+    {
+        // If possible, include the destructor only when it's needed
+        private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types);
+    }
+    else
+    {
+        // If we can't tell, always include it, even when it does nothing
+        private enum includeDtor = true;
+    }
+
+    static if (includeDtor)
+    {
+        /// Calls the destructor of the `SumType`'s current value.
+        ~this()
+        {
+            this.match!destroyIfOwner;
+        }
+    }
+
+    invariant
+    {
+        this.match!((ref value) {
+            static if (is(typeof(value) == class))
+            {
+                if (value !is null)
+                {
+                    assert(value);
+                }
+            }
+            else static if (is(typeof(value) == struct))
+            {
+                assert(&value);
+            }
+        });
+    }
+
+    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
+    version (StdDdoc)
+    {
+        /**
+         * Returns a string representation of the `SumType`'s current value.
+         *
+         * Not available when compiled with `-betterC`.
+         */
+        string toString(this This)();
+
+        /**
+         * Handles formatted writing of the `SumType`'s current value.
+         *
+         * Not available when compiled with `-betterC`.
+         *
+         * Params:
+         *   sink = Output range to write to.
+         *   fmt = Format specifier to use.
+         *
+         * See_Also: $(REF formatValue, std,format)
+         */
+        void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt);
+    }
+
+    version (D_BetterC) {} else
+    /**
+     * Returns a string representation of the `SumType`'s current value.
+     *
+     * Not available when compiled with `-betterC`.
+     */
+    string toString(this This)()
+    {
+        import std.conv : to;
+
+        return this.match!(to!string);
+    }
+
+    version (D_BetterC) {} else
+    /**
+     * Handles formatted writing of the `SumType`'s current value.
+     *
+     * Not available when compiled with `-betterC`.
+     *
+     * Params:
+     *   sink = Output range to write to.
+     *   fmt = Format specifier to use.
+     *
+     * See_Also: $(REF formatValue, std,format)
+     */
+    void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt)
+    {
+        import std.format.write : formatValue;
+
+        this.match!((ref value) {
+            formatValue(sink, value, fmt);
+        });
+    }
+
+    static if (allSatisfy!(isHashable, Map!(ConstOf, Types)))
+    {
+        // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
+        version (StdDdoc)
+        {
+            /**
+             * Returns the hash of the `SumType`'s current value.
+             *
+             * Not available when compiled with `-betterC`.
+             */
+            size_t toHash() const;
+        }
+
+        // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095
+        version (D_BetterC) {} else
+        /**
+         * Returns the hash of the `SumType`'s current value.
+         *
+         * Not available when compiled with `-betterC`.
+         */
+        size_t toHash() const
+        {
+            return this.match!hashOf;
+        }
+    }
+}
+
+// Construction
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+}
+
+// Assignment
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    x = 3.14;
+}
+
+// Self assignment
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+    y = x;
+}
+
+// Equality
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    assert(MySum(123) == MySum(123));
+    assert(MySum(123) != MySum(456));
+    assert(MySum(123) != MySum(123.0));
+    assert(MySum(123) != MySum(456.0));
+
+}
+
+// Equality of differently-qualified SumTypes
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias SumA = SumType!(int, float);
+    alias SumB = SumType!(const(int[]), int[]);
+    alias SumC = SumType!(int[], const(int[]));
+
+    int[] ma = [1, 2, 3];
+    const(int[]) ca = [1, 2, 3];
+
+    assert(const(SumA)(123) == SumA(123));
+    assert(const(SumB)(ma[]) == SumB(ca[]));
+    assert(const(SumC)(ma[]) == SumC(ca[]));
+}
+
+// Imported types
+@safe unittest
+{
+    import std.typecons : Tuple;
+
+    alias MySum = SumType!(Tuple!(int, int));
+}
+
+// const and immutable types
+@safe unittest
+{
+    alias MySum = SumType!(const(int[]), immutable(float[]));
+}
+
+// Recursive types
+@safe unittest
+{
+    alias MySum = SumType!(This*);
+    assert(is(MySum.Types[0] == MySum*));
+}
+
+// Allowed types
+@safe unittest
+{
+    import std.meta : AliasSeq;
+
+    alias MySum = SumType!(int, float, This*);
+
+    assert(is(MySum.Types == AliasSeq!(int, float, MySum*)));
+}
+
+// Types with destructors and postblits
+@system unittest
+{
+    int copies;
+
+    static struct Test
+    {
+        bool initialized = false;
+        int* copiesPtr;
+
+        this(this) { (*copiesPtr)++; }
+        ~this() { if (initialized) (*copiesPtr)--; }
+    }
+
+    alias MySum = SumType!(int, Test);
+
+    Test t = Test(true, &copies);
+
+    {
+        MySum x = t;
+        assert(copies == 1);
+    }
+    assert(copies == 0);
+
+    {
+        MySum x = 456;
+        assert(copies == 0);
+    }
+    assert(copies == 0);
+
+    {
+        MySum x = t;
+        assert(copies == 1);
+        x = 456;
+        assert(copies == 0);
+    }
+
+    {
+        MySum x = 456;
+        assert(copies == 0);
+        x = t;
+        assert(copies == 1);
+    }
+
+    {
+        MySum x = t;
+        MySum y = x;
+        assert(copies == 2);
+    }
+
+    {
+        MySum x = t;
+        MySum y;
+        y = x;
+        assert(copies == 2);
+    }
+}
+
+// Doesn't destroy reference types
+// Disabled in BetterC due to use of classes
+version (D_BetterC) {} else
+@system unittest
+{
+    bool destroyed;
+
+    class C
+    {
+        ~this()
+        {
+            destroyed = true;
+        }
+    }
+
+    struct S
+    {
+        ~this() {}
+    }
+
+    alias MySum = SumType!(S, C);
+
+    C c = new C();
+    {
+        MySum x = c;
+        destroyed = false;
+    }
+    assert(!destroyed);
+
+    {
+        MySum x = c;
+        destroyed = false;
+        x = S();
+        assert(!destroyed);
+    }
+}
+
+// Types with @disable this()
+@safe unittest
+{
+    static struct NoInit
+    {
+        @disable this();
+    }
+
+    alias MySum = SumType!(NoInit, int);
+
+    assert(!__traits(compiles, MySum()));
+    auto _ = MySum(42);
+}
+
+// const SumTypes
+version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117
+@safe unittest
+{
+    auto _ = const(SumType!(int[]))([1, 2, 3]);
+}
+
+// Equality of const SumTypes
+@safe unittest
+{
+    alias MySum = SumType!int;
+
+    auto _ = const(MySum)(123) == const(MySum)(456);
+}
+
+// Compares reference types using value equality
+@safe unittest
+{
+    import std.array : staticArray;
+
+    static struct Field {}
+    static struct Struct { Field[] fields; }
+    alias MySum = SumType!Struct;
+
+    static arr1 = staticArray([Field()]);
+    static arr2 = staticArray([Field()]);
+
+    auto a = MySum(Struct(arr1[]));
+    auto b = MySum(Struct(arr2[]));
+
+    assert(a == b);
+}
+
+// toString
+// Disabled in BetterC due to use of std.conv.text
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.conv : text;
+
+    static struct Int { int i; }
+    static struct Double { double d; }
+    alias Sum = SumType!(Int, Double);
+
+    assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text);
+    assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text);
+    assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text);
+}
+
+// string formatting
+// Disabled in BetterC due to use of std.format.format
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.format : format;
+
+    SumType!int x = 123;
+
+    assert(format!"%s"(x) == format!"%s"(123));
+    assert(format!"%x"(x) == format!"%x"(123));
+}
+
+// string formatting of qualified SumTypes
+// Disabled in BetterC due to use of std.format.format and dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.format : format;
+
+    int[] a = [1, 2, 3];
+    const(SumType!(int[])) x = a;
+
+    assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a));
+}
+
+// Github issue #16
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias Node = SumType!(This[], string);
+
+    // override inference of @system attribute for cyclic functions
+    assert((() @trusted =>
+        Node([Node([Node("x")])])
+        ==
+        Node([Node([Node("x")])])
+    )());
+}
+
+// Github issue #16 with const
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias Node = SumType!(const(This)[], string);
+
+    // override inference of @system attribute for cyclic functions
+    assert((() @trusted =>
+        Node([Node([Node("x")])])
+        ==
+        Node([Node([Node("x")])])
+    )());
+}
+
+// Stale pointers
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@system unittest
+{
+    alias MySum = SumType!(ubyte, void*[2]);
+
+    MySum x = [null, cast(void*) 0x12345678];
+    void** p = &x.get!(void*[2])[1];
+    x = ubyte(123);
+
+    assert(*p != cast(void*) 0x12345678);
+}
+
+// Exception-safe assignment
+// Disabled in BetterC due to use of exceptions
+version (D_BetterC) {} else
+@safe unittest
+{
+    static struct A
+    {
+        int value = 123;
+    }
+
+    static struct B
+    {
+        int value = 456;
+        this(this) { throw new Exception("oops"); }
+    }
+
+    alias MySum = SumType!(A, B);
+
+    MySum x;
+    try
+    {
+        x = B();
+    }
+    catch (Exception e) {}
+
+    assert(
+        (x.tag == 0 && x.get!A.value == 123) ||
+        (x.tag == 1 && x.get!B.value == 456)
+    );
+}
+
+// Types with @disable this(this)
+@safe unittest
+{
+    import core.lifetime : move;
+
+    static struct NoCopy
+    {
+        @disable this(this);
+    }
+
+    alias MySum = SumType!NoCopy;
+
+    NoCopy lval = NoCopy();
+
+    MySum x = NoCopy();
+    MySum y = NoCopy();
+
+
+    assert(!__traits(compiles, SumType!NoCopy(lval)));
+
+    y = NoCopy();
+    y = move(x);
+    assert(!__traits(compiles, y = lval));
+    assert(!__traits(compiles, y = x));
+
+    bool b = x == y;
+}
+
+// Github issue #22
+// Disabled in BetterC due to use of std.typecons.Nullable
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.typecons;
+
+    static struct A
+    {
+        SumType!(Nullable!int) a = Nullable!int.init;
+    }
+}
+
+// Static arrays of structs with postblits
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    static struct S
+    {
+        int n;
+        this(this) { n++; }
+    }
+
+    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;
+
+    assert(xval != yval);
+}
+
+// Replacement does not happen inside SumType
+// Disabled in BetterC due to use of associative arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.typecons : Tuple, ReplaceTypeUnless;
+    alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]];
+    alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A);
+    static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]]));
+}
+
+// Supports nested self-referential SumTypes
+@safe unittest
+{
+    import std.typecons : Tuple, Flag;
+    alias Nat = SumType!(Flag!"0", Tuple!(This*));
+    alias Inner = SumType!Nat;
+    alias Outer = SumType!(Nat*, Tuple!(This*, This*));
+}
+
+// Self-referential SumTypes inside Algebraic
+// Disabled in BetterC due to use of std.variant.Algebraic
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.variant : Algebraic;
+
+    alias T = Algebraic!(SumType!(This*));
+
+    assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*));
+}
+
+// Doesn't call @system postblits in @safe code
+@safe unittest
+{
+    static struct SystemCopy { @system this(this) {} }
+    SystemCopy original;
+
+    assert(!__traits(compiles, () @safe
+            {
+        SumType!SystemCopy copy = original;
+    }));
+
+    assert(!__traits(compiles, () @safe
+            {
+        SumType!SystemCopy copy; copy = original;
+    }));
+}
+
+// Doesn't overwrite pointers in @safe code
+@safe unittest
+{
+    alias MySum = SumType!(int*, int);
+
+    MySum x;
+
+    assert(!__traits(compiles, () @safe
+            {
+        x = 123;
+    }));
+
+    assert(!__traits(compiles, () @safe
+            {
+        x = MySum(123);
+    }));
+}
+
+// Types with invariants
+// Disabled in BetterC due to use of exceptions
+version (D_BetterC) {} else
+version (D_Invariants)
+@system unittest
+{
+    import std.exception : assertThrown;
+    import core.exception : AssertError;
+
+    struct S
+    {
+        int i;
+        invariant { assert(i >= 0); }
+    }
+
+    class C
+    {
+        int i;
+        invariant { assert(i >= 0); }
+    }
+
+    SumType!S x;
+    x.match!((ref v) { v.i = -1; });
+    assertThrown!AssertError(assert(&x));
+
+    SumType!C y = new C();
+    y.match!((ref v) { v.i = -1; });
+    assertThrown!AssertError(assert(&y));
+}
+
+// Calls value postblit on self-assignment
+@safe unittest
+{
+    static struct S
+    {
+        int n;
+        this(this) { n++; }
+    }
+
+    SumType!S x = S();
+    SumType!S y;
+    y = x;
+
+    auto xval = x.get!S.n;
+    auto yval = y.get!S.n;
+
+    assert(xval != yval);
+}
+
+// Github issue #29
+@safe unittest
+{
+    alias A = SumType!string;
+
+    @safe A createA(string arg)
+    {
+        return A(arg);
+    }
+
+    @safe void test()
+    {
+        A a = createA("");
+    }
+}
+
+// SumTypes as associative array keys
+// Disabled in BetterC due to use of associative arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    int[SumType!(int, string)] aa;
+}
+
+// toString with non-copyable types
+// Disabled in BetterC due to use of std.conv.to (in toString)
+version (D_BetterC) {} else
+@safe unittest
+{
+    struct NoCopy
+    {
+        @disable this(this);
+    }
+
+    SumType!NoCopy x;
+
+    auto _ = x.toString();
+}
+
+// Can use the result of assignment
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum a = MySum(123);
+    MySum b = MySum(3.14);
+
+    assert((a = b) == b);
+    assert((a = MySum(123)) == MySum(123));
+    assert((a = 3.14) == MySum(3.14));
+    assert(((a = b) = MySum(123)) == MySum(123));
+}
+
+// Types with copy constructors
+@safe unittest
+{
+    static struct S
+    {
+        int n;
+
+        this(ref return scope inout S other) inout
+        {
+            n = other.n + 1;
+        }
+    }
+
+    SumType!S x = S();
+    SumType!S y = x;
+
+    auto xval = x.get!S.n;
+    auto yval = y.get!S.n;
+
+    assert(xval != yval);
+}
+
+// Copyable by generated copy constructors
+@safe unittest
+{
+    static struct Inner
+    {
+        ref this(ref inout Inner other) {}
+    }
+
+    static struct Outer
+    {
+        SumType!Inner inner;
+    }
+
+    Outer x;
+    Outer y = x;
+}
+
+// Types with qualified copy constructors
+@safe unittest
+{
+    static struct ConstCopy
+    {
+        int n;
+        this(inout int n) inout { this.n = n; }
+        this(ref const typeof(this) other) const { this.n = other.n; }
+    }
+
+    static struct ImmutableCopy
+    {
+        int n;
+        this(inout int n) inout { this.n = n; }
+        this(ref immutable typeof(this) other) immutable { this.n = other.n; }
+    }
+
+    const SumType!ConstCopy x = const(ConstCopy)(1);
+    immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1);
+}
+
+// Types with disabled opEquals
+@safe unittest
+{
+    static struct S
+    {
+        @disable bool opEquals(const S rhs) const;
+    }
+
+    auto _ = SumType!S(S());
+}
+
+// Types with non-const opEquals
+@safe unittest
+{
+    static struct S
+    {
+        int i;
+        bool opEquals(S rhs) { return i == rhs.i; }
+    }
+
+    auto _ = SumType!S(S(123));
+}
+
+// Incomparability of different SumTypes
+@safe unittest
+{
+    SumType!(int, string) x = 123;
+    SumType!(string, int) y = 123;
+
+    assert(!__traits(compiles, x != y));
+}
+
+// Self-reference in return/parameter type of function pointer member
+// Disabled in BetterC due to use of delegates
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias T = SumType!(int, This delegate(This));
+}
+
+// Construction and assignment from implicitly-convertible lvalue
+@safe unittest
+{
+    alias MySum = SumType!bool;
+
+    const(bool) b = true;
+
+    MySum x = b;
+    MySum y; y = b;
+}
+
+// @safe assignment to the only pointer type in a SumType
+@safe unittest
+{
+    SumType!(string, int) sm = 123;
+    sm = "this should be @safe";
+}
+
+// Immutable member type with copy constructor
+// https://issues.dlang.org/show_bug.cgi?id=22572
+@safe unittest
+{
+    static struct CopyConstruct
+    {
+        this(ref inout CopyConstruct other) inout {}
+    }
+
+    static immutable struct Value
+    {
+        CopyConstruct c;
+    }
+
+    SumType!Value s;
+}
+
+// Construction of inout-qualified SumTypes
+// https://issues.dlang.org/show_bug.cgi?id=22901
+@safe unittest
+{
+    static inout(SumType!(int[])) example(inout(int[]) arr)
+    {
+        return inout(SumType!(int[]))(arr);
+    }
+}
+
+// Assignment of struct with overloaded opAssign in CTFE
+// https://issues.dlang.org/show_bug.cgi?id=23182
+@safe unittest
+{
+    static struct HasOpAssign
+    {
+        void opAssign(HasOpAssign rhs) {}
+    }
+
+    static SumType!HasOpAssign test()
+    {
+        SumType!HasOpAssign s;
+        // Test both overloads
+        s = HasOpAssign();
+        s = SumType!HasOpAssign();
+        return s;
+    }
+
+    // Force CTFE
+    enum result = test();
+}
+
+/// True if `T` is an instance of the `SumType` template, otherwise false.
+private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...);
+
+@safe unittest
+{
+    static struct Wrapper
+    {
+        SumType!int s;
+        alias s this;
+    }
+
+    assert(isSumTypeInstance!(SumType!int));
+    assert(!isSumTypeInstance!Wrapper);
+}
+
+/// True if `T` is a [SumType] or implicitly converts to one, otherwise false.
+enum bool isSumType(T) = is(T : SumType!Args, Args...);
+
+///
+@safe unittest
+{
+    static struct ConvertsToSumType
+    {
+        SumType!int payload;
+        alias payload this;
+    }
+
+    static struct ContainsSumType
+    {
+        SumType!int payload;
+    }
+
+    assert(isSumType!(SumType!int));
+    assert(isSumType!ConvertsToSumType);
+    assert(!isSumType!ContainsSumType);
+}
+
+/**
+ * Calls a type-appropriate function with the value held in a [SumType].
+ *
+ * For each possible type the [SumType] can hold, the given handlers are
+ * checked, in order, to see whether they accept a single argument of that type.
+ * The first one that does is chosen as the match for that type. (Note that the
+ * first match may not always be the most exact match.
+ * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for
+ * one common pitfall.)
+ *
+ * Every type must have a matching handler, and every handler must match at
+ * least one type. This is enforced at compile time.
+ *
+ * Handlers may be functions, delegates, or objects with `opCall` overloads. If
+ * a function with more than one overload is given as a handler, all of the
+ * overloads are considered as potential matches.
+ *
+ * Templated handlers are also accepted, and will match any type for which they
+ * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See
+ * ["Introspection-based matching"](#introspection-based-matching) for an
+ * example of templated handler usage.
+ *
+ * If multiple [SumType]s are passed to match, their values are passed to the
+ * handlers as separate arguments, and matching is done for each possible
+ * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for
+ * an example.
+ *
+ * Returns:
+ *   The value returned from the handler that matches the currently-held type.
+ *
+ * See_Also: $(REF visit, std,variant)
+ */
+template match(handlers...)
+{
+    import std.typecons : Yes;
+
+    /**
+     * The actual `match` function.
+     *
+     * Params:
+     *   args = One or more [SumType] objects.
+     */
+    auto ref match(SumTypes...)(auto ref SumTypes args)
+    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
+    {
+        return matchImpl!(Yes.exhaustive, handlers)(args);
+    }
+}
+
+/** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches))
+ *
+ * Sometimes, implicit conversions may cause a handler to match more types than
+ * intended. The example below shows two solutions to this problem.
+ */
+@safe unittest
+{
+    alias Number = SumType!(double, int);
+
+    Number x;
+
+    // Problem: because int implicitly converts to double, the double
+    // handler is used for both types, and the int handler never matches.
+    assert(!__traits(compiles,
+        x.match!(
+            (double d) => "got double",
+            (int n) => "got int"
+        )
+    ));
+
+    // Solution 1: put the handler for the "more specialized" type (in this
+    // case, int) before the handler for the type it converts to.
+    assert(__traits(compiles,
+        x.match!(
+            (int n) => "got int",
+            (double d) => "got double"
+        )
+    ));
+
+    // Solution 2: use a template that only accepts the exact type it's
+    // supposed to match, instead of any type that implicitly converts to it.
+    alias exactly(T, alias fun) = function (arg)
+    {
+        static assert(is(typeof(arg) == T));
+        return fun(arg);
+    };
+
+    // Now, even if we put the double handler first, it will only be used for
+    // doubles, not ints.
+    assert(__traits(compiles,
+        x.match!(
+            exactly!(double, d => "got double"),
+            exactly!(int, n => "got int")
+        )
+    ));
+}
+
+/** $(DIVID multiple-dispatch, $(H3 Multiple dispatch))
+ *
+ * Pattern matching can be performed on multiple `SumType`s at once by passing
+ * handlers with multiple arguments. This usually leads to more concise code
+ * than using nested calls to `match`, as show below.
+ */
+@safe unittest
+{
+    struct Point2D { double x, y; }
+    struct Point3D { double x, y, z; }
+
+    alias Point = SumType!(Point2D, Point3D);
+
+    version (none)
+    {
+        // This function works, but the code is ugly and repetitive.
+        // It uses three separate calls to match!
+        @safe pure nothrow @nogc
+        bool sameDimensions(Point p1, Point p2)
+        {
+            return p1.match!(
+                (Point2D _) => p2.match!(
+                    (Point2D _) => true,
+                    _ => false
+                ),
+                (Point3D _) => p2.match!(
+                    (Point3D _) => true,
+                    _ => false
+                )
+            );
+        }
+    }
+
+    // This version is much nicer.
+    @safe pure nothrow @nogc
+    bool sameDimensions(Point p1, Point p2)
+    {
+        alias doMatch = match!(
+            (Point2D _1, Point2D _2) => true,
+            (Point3D _1, Point3D _2) => true,
+            (_1, _2) => false
+        );
+
+        return doMatch(p1, p2);
+    }
+
+    Point a = Point2D(1, 2);
+    Point b = Point2D(3, 4);
+    Point c = Point3D(5, 6, 7);
+    Point d = Point3D(8, 9, 0);
+
+    assert( sameDimensions(a, b));
+    assert( sameDimensions(c, d));
+    assert(!sameDimensions(a, c));
+    assert(!sameDimensions(d, b));
+}
+
+/**
+ * Attempts to call a type-appropriate function with the value held in a
+ * [SumType], and throws on failure.
+ *
+ * Matches are chosen using the same rules as [match], but are not required to
+ * be exhaustive—in other words, a type (or combination of types) is allowed to
+ * have no matching handler. If a type without a handler is encountered at
+ * runtime, a [MatchException] is thrown.
+ *
+ * Not available when compiled with `-betterC`.
+ *
+ * Returns:
+ *   The value returned from the handler that matches the currently-held type,
+ *   if a handler was given for that type.
+ *
+ * Throws:
+ *   [MatchException], if the currently-held type has no matching handler.
+ *
+ * See_Also: $(REF tryVisit, std,variant)
+ */
+version (D_Exceptions)
+template tryMatch(handlers...)
+{
+    import std.typecons : No;
+
+    /**
+     * The actual `tryMatch` function.
+     *
+     * Params:
+     *   args = One or more [SumType] objects.
+     */
+    auto ref tryMatch(SumTypes...)(auto ref SumTypes args)
+    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
+    {
+        return matchImpl!(No.exhaustive, handlers)(args);
+    }
+}
+
+/**
+ * Thrown by [tryMatch] when an unhandled type is encountered.
+ *
+ * Not available when compiled with `-betterC`.
+ */
+version (D_Exceptions)
+class MatchException : Exception
+{
+    ///
+    pure @safe @nogc nothrow
+    this(string msg, string file = __FILE__, size_t line = __LINE__)
+    {
+        super(msg, file, line);
+    }
+}
+
+/**
+ * True if `handler` is a potential match for `Ts`, otherwise false.
+ *
+ * See the documentation for [match] for a full explanation of how matches are
+ * chosen.
+ */
+template canMatch(alias handler, Ts...)
+if (Ts.length > 0)
+{
+    enum canMatch = is(typeof((ref Ts args) => handler(args)));
+}
+
+///
+@safe unittest
+{
+    alias handleInt = (int i) => "got an int";
+
+    assert( canMatch!(handleInt, int));
+    assert(!canMatch!(handleInt, string));
+}
+
+// Includes all overloads of the given handler
+@safe unittest
+{
+    static struct OverloadSet
+    {
+        static void fun(int n) {}
+        static void fun(double d) {}
+    }
+
+    assert(canMatch!(OverloadSet.fun, int));
+    assert(canMatch!(OverloadSet.fun, double));
+}
+
+// Like aliasSeqOf!(iota(n)), but works in BetterC
+private template Iota(size_t n)
+{
+    static if (n == 0)
+    {
+        alias Iota = AliasSeq!();
+    }
+    else
+    {
+        alias Iota = AliasSeq!(Iota!(n - 1), n - 1);
+    }
+}
+
+@safe unittest
+{
+    assert(is(Iota!0 == AliasSeq!()));
+    assert(Iota!1 == AliasSeq!(0));
+    assert(Iota!3 == AliasSeq!(0, 1, 2));
+}
+
+/* The number that the dim-th argument's tag is multiplied by when
+ * converting TagTuples to and from case indices ("caseIds").
+ *
+ * Named by analogy to the stride that the dim-th index into a
+ * multidimensional static array is multiplied by to calculate the
+ * offset of a specific element.
+ */
+private size_t stride(size_t dim, lengths...)()
+{
+    import core.checkedint : mulu;
+
+    size_t result = 1;
+    bool overflow = false;
+
+    static foreach (i; 0 .. dim)
+    {
+        result = mulu(result, lengths[i], overflow);
+    }
+
+    /* The largest number matchImpl uses, numCases, is calculated with
+     * stride!(SumTypes.length), so as long as this overflow check
+     * passes, we don't need to check for overflow anywhere else.
+     */
+    assert(!overflow, "Integer overflow");
+    return result;
+}
+
+private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
+{
+    auto ref matchImpl(SumTypes...)(auto ref SumTypes args)
+    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
+    {
+        alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes));
+        alias TagTuple = .TagTuple!(SumTypes);
+
+        /*
+         * A list of arguments to be passed to a handler needed for the case
+         * labeled with `caseId`.
+         */
+        template handlerArgs(size_t caseId)
+        {
+            enum tags = TagTuple.fromCaseId(caseId);
+            enum argsFrom(size_t i : tags.length) = "";
+            enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~
+                ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1);
+            enum handlerArgs = argsFrom!0;
+        }
+
+        /* An AliasSeq of the types of the member values in the argument list
+         * returned by `handlerArgs!caseId`.
+         *
+         * Note that these are the actual (that is, qualified) types of the
+         * member values, which may not be the same as the types listed in
+         * the arguments' `.Types` properties.
+         */
+        template valueTypes(size_t caseId)
+        {
+            enum tags = TagTuple.fromCaseId(caseId);
+
+            template getType(size_t i)
+            {
+                enum tid = tags[i];
+                alias T = SumTypes[i].Types[tid];
+                alias getType = typeof(args[i].get!T());
+            }
+
+            alias valueTypes = Map!(getType, Iota!(tags.length));
+        }
+
+        /* The total number of cases is
+         *
+         *   Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length
+         *
+         * Or, equivalently,
+         *
+         *   ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof
+         *
+         * Conveniently, this is equal to stride!(SumTypes.length), so we can
+         * use that function to compute it.
+         */
+        enum numCases = stride!(SumTypes.length);
+
+        /* Guaranteed to never be a valid handler index, since
+         * handlers.length <= size_t.max.
+         */
+        enum noMatch = size_t.max;
+
+        // An array that maps caseIds to handler indices ("hids").
+        enum matches = ()
+        {
+            size_t[numCases] matches;
+
+            // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561
+            foreach (ref match; matches)
+            {
+                match = noMatch;
+            }
+
+            static foreach (caseId; 0 .. numCases)
+            {
+                static foreach (hid, handler; handlers)
+                {
+                    static if (canMatch!(handler, valueTypes!caseId))
+                    {
+                        if (matches[caseId] == noMatch)
+                        {
+                            matches[caseId] = hid;
+                        }
+                    }
+                }
+            }
+
+            return matches;
+        }();
+
+        import std.algorithm.searching : canFind;
+
+        // Check for unreachable handlers
+        static foreach (hid, handler; handlers)
+        {
+            static assert(matches[].canFind(hid),
+                "`handlers[" ~ toCtString!hid ~ "]` " ~
+                "of type `" ~ ( __traits(isTemplate, handler)
+                    ? "template"
+                    : typeof(handler).stringof
+                ) ~ "` " ~
+                "never matches"
+            );
+        }
+
+        // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993
+        enum handlerName(size_t hid) = "handler" ~ toCtString!hid;
+
+        static foreach (size_t hid, handler; handlers)
+        {
+            mixin("alias ", handlerName!hid, " = handler;");
+        }
+
+        immutable argsId = TagTuple(args).toCaseId;
+
+        final switch (argsId)
+        {
+            static foreach (caseId; 0 .. numCases)
+            {
+                case caseId:
+                    static if (matches[caseId] != noMatch)
+                    {
+                        return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")");
+                    }
+                    else
+                    {
+                        static if (exhaustive)
+                        {
+                            static assert(false,
+                                "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`");
+                        }
+                        else
+                        {
+                            throw new MatchException(
+                                "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`");
+                        }
+                    }
+            }
+        }
+
+        assert(false, "unreachable");
+    }
+}
+
+private enum typeCount(SumType) = SumType.Types.length;
+
+/* A TagTuple represents a single possible set of tags that `args`
+ * could have at runtime.
+ *
+ * Because D does not allow a struct to be the controlling expression
+ * of a switch statement, we cannot dispatch on the TagTuple directly.
+ * Instead, we must map each TagTuple to a unique integer and generate
+ * a case label for each of those integers.
+ *
+ * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses
+ * the same technique that's used to map index tuples to memory offsets
+ * in a multidimensional static array.
+ *
+ * For example, when `args` consists of two SumTypes with two member
+ * types each, the TagTuples corresponding to each case label are:
+ *
+ *   case 0:  TagTuple([0, 0])
+ *   case 1:  TagTuple([1, 0])
+ *   case 2:  TagTuple([0, 1])
+ *   case 3:  TagTuple([1, 1])
+ *
+ * When there is only one argument, the caseId is equal to that
+ * argument's tag.
+ */
+private struct TagTuple(SumTypes...)
+{
+    size_t[SumTypes.length] tags;
+    alias tags this;
+
+    alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes));
+
+    invariant
+    {
+        static foreach (i; 0 .. tags.length)
+        {
+            assert(tags[i] < SumTypes[i].Types.length, "Invalid tag");
+        }
+    }
+
+    this(ref const(SumTypes) args)
+    {
+        static foreach (i; 0 .. tags.length)
+        {
+            tags[i] = args[i].tag;
+        }
+    }
+
+    static TagTuple fromCaseId(size_t caseId)
+    {
+        TagTuple result;
+
+        // Most-significant to least-significant
+        static foreach_reverse (i; 0 .. result.length)
+        {
+            result[i] = caseId / stride!i;
+            caseId %= stride!i;
+        }
+
+        return result;
+    }
+
+    size_t toCaseId()
+    {
+        size_t result;
+
+        static foreach (i; 0 .. tags.length)
+        {
+            result += tags[i] * stride!i;
+        }
+
+        return result;
+    }
+}
+
+// Matching
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+
+    assert(x.match!((int v) => true, (float v) => false));
+    assert(y.match!((int v) => false, (float v) => true));
+}
+
+// Missing handlers
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+
+    assert(!__traits(compiles, x.match!((int x) => true)));
+    assert(!__traits(compiles, x.match!()));
+}
+
+// Handlers with qualified parameters
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias MySum = SumType!(int[], float[]);
+
+    MySum x = MySum([1, 2, 3]);
+    MySum y = MySum([1.0, 2.0, 3.0]);
+
+    assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
+    assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true));
+}
+
+// Handlers for qualified types
+// Disabled in BetterC due to use of dynamic arrays
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias MySum = SumType!(immutable(int[]), immutable(float[]));
+
+    MySum x = MySum([1, 2, 3]);
+
+    assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false));
+    assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
+    // Tail-qualified parameters
+    assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false));
+    assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false));
+    // Generic parameters
+    assert(x.match!((immutable v) => true));
+    assert(x.match!((const v) => true));
+    // Unqualified parameters
+    assert(!__traits(compiles,
+        x.match!((int[] v) => true, (float[] v) => false)
+    ));
+}
+
+// Delegate handlers
+// Disabled in BetterC due to use of closures
+version (D_BetterC) {} else
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    int answer = 42;
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+
+    assert(x.match!((int v) => v == answer, (float v) => v == answer));
+    assert(!y.match!((int v) => v == answer, (float v) => v == answer));
+}
+
+version (unittest)
+{
+    version (D_BetterC)
+    {
+        // std.math.isClose depends on core.runtime.math, so use a
+        // libc-based version for testing with -betterC
+        @safe pure @nogc nothrow
+        private bool isClose(double lhs, double rhs)
+        {
+            import core.stdc.math : fabs;
+
+            return fabs(lhs - rhs) < 1e-5;
+        }
+    }
+    else
+    {
+        import std.math.operations : isClose;
+    }
+}
+
+// Generic handler
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+
+    assert(x.match!(v => v*2) == 84);
+    assert(y.match!(v => v*2).isClose(6.28));
+}
+
+// Fallback to generic handler
+// Disabled in BetterC due to use of std.conv.to
+version (D_BetterC) {} else
+@safe unittest
+{
+    import std.conv : to;
+
+    alias MySum = SumType!(int, float, string);
+
+    MySum x = MySum(42);
+    MySum y = MySum("42");
+
+    assert(x.match!((string v) => v.to!int, v => v*2) == 84);
+    assert(y.match!((string v) => v.to!int, v => v*2) == 42);
+}
+
+// Multiple non-overlapping generic handlers
+@safe unittest
+{
+    import std.array : staticArray;
+
+    alias MySum = SumType!(int, float, int[], char[]);
+
+    static ints = staticArray([1, 2, 3]);
+    static chars = staticArray(['a', 'b', 'c']);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+    MySum z = MySum(ints[]);
+    MySum w = MySum(chars[]);
+
+    assert(x.match!(v => v*2, v => v.length) == 84);
+    assert(y.match!(v => v*2, v => v.length).isClose(6.28));
+    assert(w.match!(v => v*2, v => v.length) == 3);
+    assert(z.match!(v => v*2, v => v.length) == 3);
+}
+
+// Structural matching
+@safe unittest
+{
+    static struct S1 { int x; }
+    static struct S2 { int y; }
+    alias MySum = SumType!(S1, S2);
+
+    MySum a = MySum(S1(0));
+    MySum b = MySum(S2(0));
+
+    assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1);
+    assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1);
+}
+
+// Separate opCall handlers
+@safe unittest
+{
+    static struct IntHandler
+    {
+        bool opCall(int arg)
+        {
+            return true;
+        }
+    }
+
+    static struct FloatHandler
+    {
+        bool opCall(float arg)
+        {
+            return false;
+        }
+    }
+
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+
+    assert(x.match!(IntHandler.init, FloatHandler.init));
+    assert(!y.match!(IntHandler.init, FloatHandler.init));
+}
+
+// Compound opCall handler
+@safe unittest
+{
+    static struct CompoundHandler
+    {
+        bool opCall(int arg)
+        {
+            return true;
+        }
+
+        bool opCall(float arg)
+        {
+            return false;
+        }
+    }
+
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+
+    assert(x.match!(CompoundHandler.init));
+    assert(!y.match!(CompoundHandler.init));
+}
+
+// Ordered matching
+@safe unittest
+{
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+
+    assert(x.match!((int v) => true, v => false));
+}
+
+// Non-exhaustive matching
+version (D_Exceptions)
+@system unittest
+{
+    import std.exception : assertThrown, assertNotThrown;
+
+    alias MySum = SumType!(int, float);
+
+    MySum x = MySum(42);
+    MySum y = MySum(3.14);
+
+    assertNotThrown!MatchException(x.tryMatch!((int n) => true));
+    assertThrown!MatchException(y.tryMatch!((int n) => true));
+}
+
+// Non-exhaustive matching in @safe code
+version (D_Exceptions)
+@safe unittest
+{
+    SumType!(int, float) x;
+
+    auto _ = x.tryMatch!(
+        (int n) => n + 1,
+    );
+}
+
+// Handlers with ref parameters
+@safe unittest
+{
+    alias Value = SumType!(long, double);
+
+    auto value = Value(3.14);
+
+    value.match!(
+        (long) {},
+        (ref double d) { d *= 2; }
+    );
+
+    assert(value.get!double.isClose(6.28));
+}
+
+// Unreachable handlers
+@safe unittest
+{
+    alias MySum = SumType!(int, string);
+
+    MySum s;
+
+    assert(!__traits(compiles,
+        s.match!(
+            (int _) => 0,
+            (string _) => 1,
+            (double _) => 2
+        )
+    ));
+
+    assert(!__traits(compiles,
+        s.match!(
+            _ => 0,
+            (int _) => 1
+        )
+    ));
+}
+
+// Unsafe handlers
+@system unittest
+{
+    SumType!int x;
+    alias unsafeHandler = (int x) @system { return; };
+
+    assert(!__traits(compiles, () @safe
+            {
+        x.match!unsafeHandler;
+    }));
+
+    auto test() @system
+    {
+        return x.match!unsafeHandler;
+    }
+}
+
+// Overloaded handlers
+@safe unittest
+{
+    static struct OverloadSet
+    {
+        static string fun(int i) { return "int"; }
+        static string fun(double d) { return "double"; }
+    }
+
+    alias MySum = SumType!(int, double);
+
+    MySum a = 42;
+    MySum b = 3.14;
+
+    assert(a.match!(OverloadSet.fun) == "int");
+    assert(b.match!(OverloadSet.fun) == "double");
+}
+
+// Overload sets that include SumType arguments
+@safe unittest
+{
+    alias Inner = SumType!(int, double);
+    alias Outer = SumType!(Inner, string);
+
+    static struct OverloadSet
+    {
+        @safe:
+        static string fun(int i) { return "int"; }
+        static string fun(double d) { return "double"; }
+        static string fun(string s) { return "string"; }
+        static string fun(Inner i) { return i.match!fun; }
+        static string fun(Outer o) { return o.match!fun; }
+    }
+
+    Outer a = Inner(42);
+    Outer b = Inner(3.14);
+    Outer c = "foo";
+
+    assert(OverloadSet.fun(a) == "int");
+    assert(OverloadSet.fun(b) == "double");
+    assert(OverloadSet.fun(c) == "string");
+}
+
+// Overload sets with ref arguments
+@safe unittest
+{
+    static struct OverloadSet
+    {
+        static void fun(ref int i) { i = 42; }
+        static void fun(ref double d) { d = 3.14; }
+    }
+
+    alias MySum = SumType!(int, double);
+
+    MySum x = 0;
+    MySum y = 0.0;
+
+    x.match!(OverloadSet.fun);
+    y.match!(OverloadSet.fun);
+
+    assert(x.match!((value) => is(typeof(value) == int) && value == 42));
+    assert(y.match!((value) => is(typeof(value) == double) && value == 3.14));
+}
+
+// Overload sets with templates
+@safe unittest
+{
+    import std.traits : isNumeric;
+
+    static struct OverloadSet
+    {
+        static string fun(string arg)
+        {
+            return "string";
+        }
+
+        static string fun(T)(T arg)
+        if (isNumeric!T)
+        {
+            return "numeric";
+        }
+    }
+
+    alias MySum = SumType!(int, string);
+
+    MySum x = 123;
+    MySum y = "hello";
+
+    assert(x.match!(OverloadSet.fun) == "numeric");
+    assert(y.match!(OverloadSet.fun) == "string");
+}
+
+// Github issue #24
+@safe unittest
+{
+    void test() @nogc
+    {
+        int acc = 0;
+        SumType!int(1).match!((int x) => acc += x);
+    }
+}
+
+// Github issue #31
+@safe unittest
+{
+    void test() @nogc
+    {
+        int acc = 0;
+
+        SumType!(int, string)(1).match!(
+            (int x) => acc += x,
+            (string _) => 0,
+        );
+    }
+}
+
+// Types that `alias this` a SumType
+@safe unittest
+{
+    static struct A {}
+    static struct B {}
+    static struct D { SumType!(A, B) value; alias value this; }
+
+    auto _ = D().match!(_ => true);
+}
+
+// Multiple dispatch
+@safe unittest
+{
+    alias MySum = SumType!(int, string);
+
+    static int fun(MySum x, MySum y)
+    {
+        import std.meta : Args = AliasSeq;
+
+        return Args!(x, y).match!(
+            (int    xv, int    yv) => 0,
+            (string xv, int    yv) => 1,
+            (int    xv, string yv) => 2,
+            (string xv, string yv) => 3
+        );
+    }
+
+    assert(fun(MySum(0),  MySum(0))  == 0);
+    assert(fun(MySum(""), MySum(0))  == 1);
+    assert(fun(MySum(0),  MySum("")) == 2);
+    assert(fun(MySum(""), MySum("")) == 3);
+}
+
+// inout SumTypes
+@safe unittest
+{
+    inout(int[]) fun(inout(SumType!(int[])) x)
+    {
+        return x.match!((inout(int[]) a) => a);
+    }
+}
+
+private void destroyIfOwner(T)(ref T value)
+{
+    static if (hasElaborateDestructor!T)
+    {
+        destroy(value);
+    }
+}
diff --git a/src/ext_depends/D-YAML/source/dyaml/test/tokens.d b/src/ext_depends/D-YAML/source/dyaml/test/tokens.d
index c099647..d3dce6e 100644
--- a/src/ext_depends/D-YAML/source/dyaml/test/tokens.d
+++ b/src/ext_depends/D-YAML/source/dyaml/test/tokens.d
@@ -21,7 +21,7 @@ module dyaml.test.tokens;
     static auto scanTestCommon(string filename) @safe
     {
         ubyte[] yamlData = cast(ubyte[])readText(filename).dup;
-        return Scanner(new Reader(yamlData));
+        return Scanner(new Reader(yamlData, filename));
     }
 
     /**
diff --git a/src/ext_depends/D-YAML/test/data/emojianchor.canonical b/src/ext_depends/D-YAML/test/data/emojianchor.canonical
new file mode 100644
index 0000000..8a71040
--- /dev/null
+++ b/src/ext_depends/D-YAML/test/data/emojianchor.canonical
@@ -0,0 +1,5 @@
+%YAML 1.1
+---
+!!seq [
+  !!str "unicode anchor"
+]
diff --git a/src/ext_depends/D-YAML/test/data/emojianchor.data b/src/ext_depends/D-YAML/test/data/emojianchor.data
new file mode 100644
index 0000000..72c1c37
--- /dev/null
+++ b/src/ext_depends/D-YAML/test/data/emojianchor.data
@@ -0,0 +1,2 @@
+---
+- &😁 unicode anchor
diff --git a/src/ext_depends/D-YAML/test/data/invalid-anchor-1.loader-error b/src/ext_depends/D-YAML/test/data/invalid-anchor-1.loader-error
deleted file mode 100644
index fcf7d0f..0000000
--- a/src/ext_depends/D-YAML/test/data/invalid-anchor-1.loader-error
+++ /dev/null
@@ -1 +0,0 @@
---- &?  foo # we allow only ascii and numeric characters in anchor names.
diff --git a/src/ext_depends/D-YAML/test/data/invalid-anchor-2.loader-error b/src/ext_depends/D-YAML/test/data/invalid-anchor-2.loader-error
deleted file mode 100644
index bfc4ff0..0000000
--- a/src/ext_depends/D-YAML/test/data/invalid-anchor-2.loader-error
+++ /dev/null
@@ -1,8 +0,0 @@
----
-- [
-    &correct foo,
-    *correct,
-    *correct]   # still correct
-- *correct: still correct
-- &correct-or-not[foo, bar]
-
diff --git a/src/ext_depends/D-YAML/test/data/invalid-anchor.loader-error b/src/ext_depends/D-YAML/test/data/invalid-anchor.loader-error
new file mode 100644
index 0000000..bfc4ff0
--- /dev/null
+++ b/src/ext_depends/D-YAML/test/data/invalid-anchor.loader-error
@@ -0,0 +1,8 @@
+---
+- [
+    &correct foo,
+    *correct,
+    *correct]   # still correct
+- *correct: still correct
+- &correct-or-not[foo, bar]
+
-- 
cgit v1.2.3