From dec29301d243ebe9018a9a9317351cee03d02312 Mon Sep 17 00:00:00 2001 From: Inkrementator <70717315+Inkrementator@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:55:33 +0100 Subject: [PATCH 1/2] variant: Support get!Variant variant.get!Variant used to segfault, but after PR #10697 it only threw an exception. The reason for that was that get!Variant called the `handler` with OpID.get. Handler is a function pointer to a templated function parametrized on the type currently wrapped by Variant. Because Variant generally get flattened, so `Variant(Variant(4))` just wraps the value `4`, instead of `Variant(4)`, the wrapped type (should generally) not be "Variant", and thus the OpID.get operation will fail. This couldn't be fixed in the handler, because OpID.get uses typeinfo for the target type, but VariantN is a template, so afaik we can't really test it against the infinite set of valid VariantN instantiations. `get` still gets the target type as a template parameter, so we can handle the problem there. Supporting get!Variant incidentally also fixed #10518. Adding variant inside an associative array of variants failed: ``` Variant variant = Variant([ "one": Variant(1), ]); variant["four"] = Variant(4); ``` This was because `opIndexAssign` wrapps the index as well as the value you want to assign in a Variant again. ``` Variant opIndexAssign(T, N)(T value, N i) { Variant[2] args = [ Variant(value), Variant(i) ]; fptr(OpID.indexAssign, &store, &args) == 0 || assert(false); return args[0]; } ``` The handler of `variant` containing the AA is parametrized with `Variant[string]`, but opIndexAssign passes a variant of type int instead of Variant(Variant(int)) due to the aformentioned flattening. Eventually in the handler, it ends up calling `Variant(i).get!Variant`, which crashed. For the github automation: Fixes #10431 Fixes #10518 --- std/variant.d | 72 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/std/variant.d b/std/variant.d index a094aeb3409..d6cd5964ec0 100644 --- a/std/variant.d +++ b/std/variant.d @@ -862,6 +862,24 @@ public: alias R = shared Unqual!T; else alias R = Unqual!T; + + /* Because Variant generally get flattened, so `Variant(Variant(4))` just wraps the value + * `4`, instead of `Variant(4)`, OpID.get will not always find VariantN in the allowed types, + * thus we must intercept it here. + * There might be exceptions to this rule when wrapping VariantN with different template + * parameters, but then opAssign will be called from the constructor and verify that the + * conversion is allowed. + */ + static if (is(R == VariantN!(tsize, Types), size_t tsize, Types...) && tsize >= maxDataSize) + { + /* Casting away the qualifier of this Variant should be ok, the wrapped value + * and it's type will be unaffected. But some amount of casting is necessary, + * because the constructor will eventually call the handler function of `this`, + * which is a function pointer and thus doesn't support inout qualifier. + */ + return T(cast(Unqual!(typeof(this)))this); + } + auto buf = tuple(typeid(T), cast(R*)&result); if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf)) @@ -3281,25 +3299,51 @@ if (isAlgebraic!VariantType && Handler.length > 0) { import std.exception : assertThrown; - struct Huge { - real a, b, c, d, e, f, g; - } - Huge h = {1,1,1,1,1,1,1}; + // Huge: values bigger than size (32 bytes) get moved to the heap and treated differently + alias Huge = ubyte[32 + 1]; + Huge h; + h[0] = 1; Variant variant = Variant([ "one": Variant(1), ]); - // Testing that this doesn't segfault. Future work might make enable this - assertThrown!VariantException(variant["three"] = 3); - assertThrown!VariantException(variant["four"] = Variant(4)); - /* Storing huge works too, value will moved to the heap - * Testing this as a regression test here as the AA handling code is still somewhat brittle and might add changes - * that depend payload size in the future - */ - assertThrown!VariantException(variant["huge"] = Variant(h)); - /+ + variant["three"] = 3; + variant["four"] = Variant(4); + variant["huge"] = Variant(h); assert(variant["one"] == Variant(1)); assert(variant["three"] == Variant(3)); assert(variant["three"] == 3); assert(variant["huge"] == Variant(h)); - +/ + assert(variant["huge"] == h); +} + +// https://github.com/dlang/phobos/issues/10431 +@system unittest +{ + int[] arr = [1,2]; + Variant v = Variant(arr); + auto result = v.get!Variant; + // Reference stays intact + result[0] = 5; + // but the slice object is a different one + result ~= 3; + assert(arr == [5,2]); + assert(result == [5,2,3]); +} + +// Verify that `get!Variant` doesn't cast away qualifier like "immutable" of wrapped reference type +@system unittest +{ + import std.exception : assertThrown; + immutable(int[]) arr = [1,2]; + Variant v = Variant(arr); + auto result = v.get!Variant; + // Note: Since get returns a copy, the qualifier of the array is stripped + // but the pointed-to data keeps it, so this is fine. + assert(result.type == typeid(immutable(int)[])); + assertThrown!VariantException(result[0] = 0); + // That said, appending still doesn't work and gives a nonsensical error message + assertThrown!(VariantException)( + result ~= 3, + msg: "Variant: attempting to use incompatible types int and immutable(int)[]" + ); } From 981a6a422a4363bf54de8849963b0d29a23c5836 Mon Sep 17 00:00:00 2001 From: Inkrementator <70717315+Inkrementator@users.noreply.github.com> Date: Fri, 9 Jan 2026 01:32:06 +0100 Subject: [PATCH 2/2] variant: Verify that Variant[] within Variant[] can be assigned This bug also depended on get!Variant working. Fixes #9980 --- std/variant.d | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/std/variant.d b/std/variant.d index d6cd5964ec0..6b0943b058b 100644 --- a/std/variant.d +++ b/std/variant.d @@ -3347,3 +3347,18 @@ if (isAlgebraic!VariantType && Handler.length > 0) msg: "Variant: attempting to use incompatible types int and immutable(int)[]" ); } + +// https://github.com/dlang/phobos/issues/9980 +@system unittest +{ + import std.stdio; + Variant[] top, bottom; + top = new Variant[](1); + bottom = new Variant[](1); + + bottom[0] = "bar"; + top[0] = bottom; + assert(top[0][0] == "bar"); + top[0][0] = "foo"; + assert(top[0][0] == "foo"); +}