aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends/D-YAML
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2023-06-04 20:24:27 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2023-06-04 20:24:27 -0400
commit7973da975eac7cf5155a140e4cb4f68d5cbb1eb9 (patch)
tree447a05d248c217b75b97c497d0a172204eeb8d7f /src/ext_depends/D-YAML
parentnix use overlays when convenient (diff)
make set_depends (dyaml update)
Diffstat (limited to 'src/ext_depends/D-YAML')
-rw-r--r--src/ext_depends/D-YAML/.github/workflows/d.yml20
-rw-r--r--src/ext_depends/D-YAML/.gitignore1
-rw-r--r--src/ext_depends/D-YAML/dub.json12
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/composer.d38
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/constructor.d4
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/emitter.d17
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/escapes.d16
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/exception.d19
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/loader.d14
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/node.d263
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/resolver.d10
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/scanner.d52
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/serializer.d2
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/stdsumtype.d2627
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/test/tokens.d2
-rw-r--r--src/ext_depends/D-YAML/test/data/emojianchor.canonical5
-rw-r--r--src/ext_depends/D-YAML/test/data/emojianchor.data2
-rw-r--r--src/ext_depends/D-YAML/test/data/invalid-anchor-1.loader-error1
-rw-r--r--src/ext_depends/D-YAML/test/data/invalid-anchor.loader-error (renamed from src/ext_depends/D-YAML/test/data/invalid-anchor-2.loader-error)0
19 files changed, 2949 insertions, 156 deletions
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.loader-error
index bfc4ff0..bfc4ff0 100644
--- a/src/ext_depends/D-YAML/test/data/invalid-anchor-2.loader-error
+++ b/src/ext_depends/D-YAML/test/data/invalid-anchor.loader-error