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