From fe64ac592b03037b99e5ea201c0170af2281ea4b Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sun, 16 Feb 2025 12:06:51 -0500 Subject: [PATCH 1/8] Rename SumType.get to getByIndex --- std/sumtype.d | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 5068da6de60..22d21f97e01 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -323,7 +323,7 @@ private: @trusted // Explicit return type omitted // Workaround for https://github.com/dlang/dmd/issues/20549 - ref get(size_t tid)() inout + ref getByIndex(size_t tid)() inout if (tid < Types.length) { assert(tag == tid, @@ -1154,7 +1154,7 @@ version (D_BetterC) {} else alias MySum = SumType!(ubyte, void*[2]); MySum x = [null, cast(void*) 0x12345678]; - void** p = &x.get!1[1]; + void** p = &x.getByIndex!1[1]; x = ubyte(123); assert(*p != cast(void*) 0x12345678); @@ -1186,8 +1186,8 @@ version (D_BetterC) {} else catch (Exception e) {} assert( - (x.tag == 0 && x.get!0.value == 123) || - (x.tag == 1 && x.get!1.value == 456) + (x.tag == 0 && x.getByIndex!0.value == 123) || + (x.tag == 1 && x.getByIndex!1.value == 456) ); } @@ -1246,8 +1246,8 @@ version (D_BetterC) {} else SumType!(S[1]) x = [S(0)]; SumType!(S[1]) y = x; - auto xval = x.get!0[0].n; - auto yval = y.get!0[0].n; + auto xval = x.getByIndex!0[0].n; + auto yval = y.getByIndex!0[0].n; assert(xval != yval); } @@ -1332,8 +1332,8 @@ version (D_BetterC) {} else SumType!S y; y = x; - auto xval = x.get!0.n; - auto yval = y.get!0.n; + auto xval = x.getByIndex!0.n; + auto yval = y.getByIndex!0.n; assert(xval != yval); } @@ -1407,8 +1407,8 @@ version (D_BetterC) {} else SumType!S x = S(); SumType!S y = x; - auto xval = x.get!0.n; - auto yval = y.get!0.n; + auto xval = x.getByIndex!0.n; + auto yval = y.getByIndex!0.n; assert(xval != yval); } @@ -1902,10 +1902,10 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) * argument's tag, so there's no need for TagTuple. */ enum handlerArgs(size_t caseId) = - "args[0].get!(" ~ toCtString!caseId ~ ")()"; + "args[0].getByIndex!(" ~ toCtString!caseId ~ ")()"; alias valueTypes(size_t caseId) = - typeof(args[0].get!(caseId)()); + typeof(args[0].getByIndex!(caseId)()); enum numCases = SumTypes[0].Types.length; } @@ -1931,7 +1931,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) template getType(size_t i) { - alias getType = typeof(args[i].get!(tags[i])()); + alias getType = typeof(args[i].getByIndex!(tags[i])()); } alias valueTypes = Map!(getType, Iota!(tags.length)); @@ -2156,7 +2156,7 @@ private template handlerArgs(size_t caseId, typeCounts...) { handlerArgs = AliasSeq!( handlerArgs, - "args[" ~ toCtString!i ~ "].get!(" ~ toCtString!(tags[i]) ~ ")(), " + "args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), " ); } } @@ -2420,7 +2420,7 @@ version (D_Exceptions) (ref double d) { d *= 2; } ); - assert(value.get!1.isClose(6.28)); + assert(value.getByIndex!1.isClose(6.28)); } // Unreachable handlers From 391ff500156fe6c7add88fd498673ffa1699fd3d Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sun, 16 Feb 2025 13:26:50 -0500 Subject: [PATCH 2/8] sumtype: add has!T to check type of SumType value --- std/sumtype.d | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/std/sumtype.d b/std/sumtype.d index 22d21f97e01..b73304fb0e9 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -2642,6 +2642,109 @@ version (D_Exceptions) })); } +/** + * Checks whether a `SumType` contains a value of a given type. + * + * The types must match exactly, without implicit conversions. + * + * Params: + * T = the type to check for. + */ +template has(T) +{ + /** + * The actual `has` function. + * + * Params: + * self = the `SumType` to check. + * + * Returns: true if `self` contains a `T`, otherwise false. + */ + bool has(Self)(auto ref Self self) + if (isSumType!Self) + { + return self.match!checkType; + } + + // Helper to avoid redundant template instantiations + bool checkType(Value)(ref Value value) + { + return is(Value == T); + } +} + +/// Basic usage +@safe unittest +{ + SumType!(string, double) example = "hello"; + + assert( example.has!string); + assert(!example.has!double); + + // If T isn't part of the SumType, has!T will always return false. + assert(!example.has!int); +} + +/// With type qualifiers +@safe unittest +{ + alias Example = SumType!(string, double); + + Example m = "mutable"; + const Example c = "const"; + immutable Example i = "immutable"; + + assert( m.has!string); + assert(!m.has!(const(string))); + assert(!m.has!(immutable(string))); + + assert(!c.has!string); + assert( c.has!(const(string))); + assert(!c.has!(immutable(string))); + + assert(!i.has!string); + assert(!i.has!(const(string))); + assert( i.has!(immutable(string))); +} + +/// As a predicate +version (D_BetterC) {} else +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.algorithm.comparison : equal; + + alias Example = SumType!(string, double); + + auto arr = [ + Example("foo"), + Example(0), + Example("bar"), + Example(1), + Example(2), + Example("baz") + ]; + + auto strings = arr.filter!(has!string); + auto nums = arr.filter!(has!double); + + assert(strings.equal([Example("foo"), Example("bar"), Example("baz")])); + assert(nums.equal([Example(0), Example(1), Example(2)])); +} + +// Non-copyable types +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy x; + + assert(x.has!NoCopy); +} + private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) From 756e50dde126717775486031a775e0ea5b066d8b Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sun, 23 Feb 2025 11:54:49 -0500 Subject: [PATCH 3/8] sumtype: add get!T to access a SumType's value --- std/sumtype.d | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/std/sumtype.d b/std/sumtype.d index b73304fb0e9..097b2900624 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -2745,6 +2745,154 @@ version (D_BetterC) {} else assert(x.has!NoCopy); } +/** + * Accesses a `SumType`'s value. + * + * The value must be of the specified type. Use [has] to check. + * + * Params: + * T = the type of the value being accessed. + */ +template get(T) +{ + /** + * The actual `get` function. + * + * Params: + * self = the `SumType` whose value is being accessed. + * + * Returns: the `SumType`'s value. + */ + auto ref T get(Self)(auto ref Self self) + if (isSumType!Self) + { + static if (__traits(isRef, self)) + return self.match!getLvalue; + else + return self.match!getRvalue; + } + + // Helpers to avoid redundant template instantiations + + ref T getLvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + return value; + } + else + { + assert(false, + "Tried to get `" ~ T.stringof ~ "`" ~ + " but found `" ~ Value.stringof ~ "`" + ); + } + } + + T getRvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + import core.lifetime : move; + + // Move if possible; otherwise fall back to copy + static if (is(typeof(move(value)))) + { + static if (isCopyable!Value) + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 + return __ctfe ? value : move(value); + else + return move(value); + } + else + return value; + } + else + { + assert(false, + "Tried to get `" ~ T.stringof ~ "`" ~ + " but found `" ~ Value.stringof ~ "`" + ); + } + } +} + +/// Basic usage +@safe unittest +{ + SumType!(string, double) example1 = "hello"; + SumType!(string, double) example2 = 3.14; + + assert(example1.get!string == "hello"); + assert(example2.get!double == 3.14); +} + +/// With type qualifiers +@safe unittest +{ + alias Example = SumType!(string, double); + + Example m = "mutable"; + const(Example) c = "const"; + immutable(Example) i = "immutable"; + + assert(m.get!string == "mutable"); + assert(c.get!(const(string)) == "const"); + assert(i.get!(immutable(string)) == "immutable"); +} + +/// As a predicate +version (D_BetterC) {} else +@safe unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.comparison : equal; + + alias Example = SumType!(string, double); + + auto arr = [Example(0), Example(1), Example(2)]; + auto values = arr.map!(get!double); + + assert(values.equal([0, 1, 2])); +} + +// Non-copyable types +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy lvalue; + auto rvalue() => SumType!NoCopy(); + + assert(lvalue.get!NoCopy == NoCopy()); + assert(rvalue.get!NoCopy == NoCopy()); +} + +// Immovable rvalues +@safe unittest +{ + auto rvalue() => const(SumType!string)("hello"); + + assert(rvalue.get!(const(string)) == "hello"); +} + +// Nontrivial rvalues at compile time +@safe unittest +{ + static struct ElaborateCopy + { + this(this) {} + } + + enum rvalue = SumType!ElaborateCopy(); + enum ctResult = rvalue.get!ElaborateCopy; + + assert(ctResult == ElaborateCopy()); +} + private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) From 688ecf2089b130fded305a7ad05fb4c1700b7685 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Mon, 24 Feb 2025 13:49:57 -0500 Subject: [PATCH 4/8] sumtype: add tryGet!T, a throwing version of get!T --- std/sumtype.d | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/std/sumtype.d b/std/sumtype.d index 097b2900624..5af86251edf 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -2893,6 +2893,184 @@ version (D_BetterC) {} else assert(ctResult == ElaborateCopy()); } +/** + * Attempt to access a `SumType`'s value. + * + * If the `SumType` does not contain a value of the specified type, an + * exception is thrown. + * + * Params: + * T = the type of the value being accessed. + */ +version (D_Exceptions) +template tryGet(T) +{ + /** + * The actual `tryGet` function. + * + * Params: + * self = the `SumType` whose value is being accessed. + * + * Throws: `MatchException` if the value does not have the expected type. + * + * Returns: the `SumType`'s value. + */ + auto ref T tryGet(Self)(auto ref Self self) + if (isSumType!Self) + { + static if (__traits(isRef, self)) + return self.match!tryGetLvalue; + else + return self.match!tryGetRvalue; + } + + // Helpers to avoid redundant template instantiations + + ref T tryGetLvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + return value; + } + else + { + throw new MatchException( + "Tried to get `" ~ T.stringof ~ "`" ~ + " but found `" ~ Value.stringof ~ "`" + ); + } + } + + T tryGetRvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + import core.lifetime : move; + + // Move if possible; otherwise fall back to copy + static if (is(typeof(move(value)))) + { + static if (isCopyable!Value) + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 + return __ctfe ? value : move(value); + else + return move(value); + } + else + return value; + } + else + { + throw new MatchException( + "Tried to get `" ~ T.stringof ~ "`" ~ + " but found `" ~ Value.stringof ~ "`" + ); + } + } +} + +/// Basic usage +version (D_Exceptions) +@safe unittest +{ + SumType!(string, double) example = "hello"; + + assert(example.tryGet!string == "hello"); + + double result = double.nan; + try + result = example.tryGet!double; + catch (MatchException e) + result = 0; + + // Exception was thrown + assert(result == 0); +} + +/// With type qualifiers +version (D_Exceptions) +@safe unittest +{ + import std.exception : assertThrown; + + const(SumType!(string, double)) example = "const"; + + // Qualifier mismatch; throws exception + assertThrown!MatchException(example.tryGet!string); + // Qualifier matches; no exception + assert(example.tryGet!(const(string)) == "const"); +} + +/// As a predicate +version (D_BetterC) {} else +@safe unittest +{ + import std.algorithm.iteration : map, sum; + import std.functional : pipe; + import std.exception : assertThrown; + + alias Example = SumType!(string, double); + + auto arr1 = [Example(0), Example(1), Example(2)]; + auto arr2 = [Example("foo"), Example("bar"), Example("baz")]; + + alias trySum = pipe!(map!(tryGet!double), sum); + + assert(trySum(arr1) == 0 + 1 + 2); + assertThrown!MatchException(trySum(arr2)); +} + +// Throws if requested type is impossible +version (D_Exceptions) +@safe unittest +{ + import std.exception : assertThrown; + + SumType!int x; + + assertThrown!MatchException(x.tryGet!string); +} + +// Non-copyable types +version (D_Exceptions) +@safe unittest +{ + static struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy lvalue; + auto rvalue() => SumType!NoCopy(); + + assert(lvalue.tryGet!NoCopy == NoCopy()); + assert(rvalue.tryGet!NoCopy == NoCopy()); +} + +// Immovable rvalues +version (D_Exceptions) +@safe unittest +{ + auto rvalue() => const(SumType!string)("hello"); + + assert(rvalue.tryGet!(const(string)) == "hello"); +} + +// Nontrivial rvalues at compile time +version (D_Exceptions) +@safe unittest +{ + static struct ElaborateCopy + { + this(this) {} + } + + enum rvalue = SumType!ElaborateCopy(); + enum ctResult = rvalue.tryGet!ElaborateCopy; + + assert(ctResult == ElaborateCopy()); +} + private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) From 7e34f06d83a41407da38c8a28f01048ad6efb464 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Mon, 24 Feb 2025 14:17:12 -0500 Subject: [PATCH 5/8] sumtype: extract common code from get and tryGet --- std/sumtype.d | 152 ++++++++++++++++++++------------------------------ 1 file changed, 60 insertions(+), 92 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 5af86251edf..b7295dc365c 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -2766,54 +2766,12 @@ template get(T) auto ref T get(Self)(auto ref Self self) if (isSumType!Self) { - static if (__traits(isRef, self)) - return self.match!getLvalue; - else - return self.match!getRvalue; - } - - // Helpers to avoid redundant template instantiations + import std.typecons : No; - ref T getLvalue(Value)(ref Value value) - { - static if (is(Value == T)) - { - return value; - } - else - { - assert(false, - "Tried to get `" ~ T.stringof ~ "`" ~ - " but found `" ~ Value.stringof ~ "`" - ); - } - } - - T getRvalue(Value)(ref Value value) - { - static if (is(Value == T)) - { - import core.lifetime : move; - - // Move if possible; otherwise fall back to copy - static if (is(typeof(move(value)))) - { - static if (isCopyable!Value) - // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 - return __ctfe ? value : move(value); - else - return move(value); - } - else - return value; - } + static if (__traits(isRef, self)) + return self.match!(getLvalue!(No.try_, T)); else - { - assert(false, - "Tried to get `" ~ T.stringof ~ "`" ~ - " but found `" ~ Value.stringof ~ "`" - ); - } + return self.match!(getRvalue!(No.try_, T)); } } @@ -2918,54 +2876,12 @@ template tryGet(T) auto ref T tryGet(Self)(auto ref Self self) if (isSumType!Self) { - static if (__traits(isRef, self)) - return self.match!tryGetLvalue; - else - return self.match!tryGetRvalue; - } - - // Helpers to avoid redundant template instantiations - - ref T tryGetLvalue(Value)(ref Value value) - { - static if (is(Value == T)) - { - return value; - } - else - { - throw new MatchException( - "Tried to get `" ~ T.stringof ~ "`" ~ - " but found `" ~ Value.stringof ~ "`" - ); - } - } - - T tryGetRvalue(Value)(ref Value value) - { - static if (is(Value == T)) - { - import core.lifetime : move; + import std.typecons : Yes; - // Move if possible; otherwise fall back to copy - static if (is(typeof(move(value)))) - { - static if (isCopyable!Value) - // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 - return __ctfe ? value : move(value); - else - return move(value); - } - else - return value; - } + static if (__traits(isRef, self)) + return self.match!(getLvalue!(Yes.try_, T)); else - { - throw new MatchException( - "Tried to get `" ~ T.stringof ~ "`" ~ - " but found `" ~ Value.stringof ~ "`" - ); - } + return self.match!(getRvalue!(Yes.try_, T)); } } @@ -3071,6 +2987,58 @@ version (D_Exceptions) assert(ctResult == ElaborateCopy()); } +private enum failedGetMessage(Expected, Actual) = + "Tried to get `" ~ Expected.stringof ~ "`" ~ + " but found `" ~ Actual.stringof ~ "`"; + +private template getLvalue(Flag!"try_" try_, T) +{ + ref T getLvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + return value; + } + else + { + static if (try_) + throw new MatchException(failedGetMessage!(T, Value)); + else + assert(false, failedGetMessage!(T, Value)); + } + } +} + +private template getRvalue(Flag!"try_" try_, T) +{ + T getRvalue(Value)(ref Value value) + { + static if (is(Value == T)) + { + import core.lifetime : move; + + // Move if possible; otherwise fall back to copy + static if (is(typeof(move(value)))) + { + static if (isCopyable!Value) + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 + return __ctfe ? value : move(value); + else + return move(value); + } + else + return value; + } + else + { + static if (try_) + throw new MatchException(failedGetMessage!(T, Value)); + else + assert(false, failedGetMessage!(T, Value)); + } + } +} + private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) From 4e51fdcd84cd464853dc904b18e20c48ed3430f1 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Tue, 25 Feb 2025 09:52:18 -0500 Subject: [PATCH 6/8] sumtype: add changelog entry for has, get, tryGet --- changelog/sumtype_procedural_api.dd | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 changelog/sumtype_procedural_api.dd diff --git a/changelog/sumtype_procedural_api.dd b/changelog/sumtype_procedural_api.dd new file mode 100644 index 00000000000..b11cfc4ebbf --- /dev/null +++ b/changelog/sumtype_procedural_api.dd @@ -0,0 +1,39 @@ +New procedural API for `std.sumtype` + +`std.sumtype` has three new convenience functions for querying and retrieving +the value of a `SumType` object. + +* `has!T` returns `true` if the `SumType` object has a value of type `T`. +* `get!T` returns the value if its type is `T`, or asserts if it is not. +* `tryGet!T` returns the value if its type is `T`, or throws an exception if it + is not. + +These functions make it easier to write code using `SumType` in a procedural +style, as opposed to the functional style encouraged by `match`. + +Example: + +--- +import std.sumtype; +import std.stdio; + +SumType!(string, double) example = "hello"; + +if (example.has!string) +{ + writeln("string: ", example.get!string); +} +else if (example.has!double) +{ + writeln("double: ", example.get!double); +} + +try +{ + writeln("double: ", example.tryGet!double); +} +catch (MatchException e) +{ + writeln("Couldn't get a double."); +} +--- From 03f7dbab9df7f104c05a79c5bba7682b7dd99f8e Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Thu, 27 Feb 2025 09:49:29 -0500 Subject: [PATCH 7/8] sumtype: make has!T.checkType helper private --- std/sumtype.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/sumtype.d b/std/sumtype.d index b7295dc365c..3157039845a 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -2667,7 +2667,7 @@ template has(T) } // Helper to avoid redundant template instantiations - bool checkType(Value)(ref Value value) + private bool checkType(Value)(ref Value value) { return is(Value == T); } From 1d3177ff2aee64e397e18ad654b813a0cfb3c3fe Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Thu, 27 Feb 2025 10:01:43 -0500 Subject: [PATCH 8/8] sumtype: prevent ambiguity in failedGetMessage --- std/sumtype.d | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 3157039845a..ab6ade0e1d8 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -2987,9 +2987,23 @@ version (D_Exceptions) assert(ctResult == ElaborateCopy()); } -private enum failedGetMessage(Expected, Actual) = - "Tried to get `" ~ Expected.stringof ~ "`" ~ - " but found `" ~ Actual.stringof ~ "`"; +private template failedGetMessage(Expected, Actual) +{ + static if (Expected.stringof == Actual.stringof) + { + enum expectedStr = __traits(fullyQualifiedName, Expected); + enum actualStr = __traits(fullyQualifiedName, Actual); + } + else + { + enum expectedStr = Expected.stringof; + enum actualStr = Actual.stringof; + } + + enum failedGetMessage = + "Tried to get `" ~ expectedStr ~ "`" ~ + " but found `" ~ actualStr ~ "`"; +} private template getLvalue(Flag!"try_" try_, T) {