// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Variant.h" #include <string> #include <ostream> #include "doctest.h" using namespace Luau; struct Foo { int x = 42; }; struct Bar { explicit Bar(int x) : prop(x * 2) { ++count; } ~Bar() { --count; } int prop; static int count; }; int Bar::count = 0; TEST_SUITE_BEGIN("Variant"); TEST_CASE("DefaultCtor") { Variant<int, Foo> v1; Variant<Foo, int> v2; REQUIRE(get_if<int>(&v1)); CHECK(*get_if<int>(&v1) == 0); CHECK(!get_if<Foo>(&v1)); REQUIRE(get_if<Foo>(&v2)); CHECK(get_if<Foo>(&v2)->x == 42); } TEST_CASE("Create") { Variant<int, Foo> v1 = 1; Variant<Foo, int> v2 = Foo{2}; Foo f = {3}; Variant<Foo, int> v3 = f; REQUIRE(get_if<int>(&v1)); CHECK(*get_if<int>(&v1) == 1); REQUIRE(get_if<Foo>(&v2)); CHECK(get_if<Foo>(&v2)->x == 2); REQUIRE(get_if<Foo>(&v3)); CHECK(get_if<Foo>(&v3)->x == 3); } TEST_CASE("Emplace") { { Variant<int, Bar> v1; CHECK(0 == Bar::count); int& i = v1.emplace<int>(5); CHECK(5 == i); CHECK(0 == Bar::count); CHECK(get_if<int>(&v1) == &i); Bar& bar = v1.emplace<Bar>(11); CHECK(22 == bar.prop); CHECK(1 == Bar::count); CHECK(get_if<Bar>(&v1) == &bar); } CHECK(0 == Bar::count); } TEST_CASE("NonPOD") { // initialize (copy) std::string s1 = "hello"; Variant<std::string, int> v1 = s1; CHECK(*get_if<std::string>(&v1) == "hello"); // initialize (move) Variant<std::string, int> v2 = std::string("hello"); CHECK(*get_if<std::string>(&v2) == "hello"); // move-assign v2 = std::string("this is a long string that doesn't fit into the small buffer"); CHECK(*get_if<std::string>(&v2) == "this is a long string that doesn't fit into the small buffer"); // copy-assign std::string s2("this is another long string, and this time we're copying it"); v2 = s2; CHECK(*get_if<std::string>(&v2) == "this is another long string, and this time we're copying it"); // copy ctor Variant<std::string, int> v3 = v2; CHECK(*get_if<std::string>(&v2) == "this is another long string, and this time we're copying it"); CHECK(*get_if<std::string>(&v3) == "this is another long string, and this time we're copying it"); // move ctor Variant<std::string, int> v4 = std::move(v3); CHECK(*get_if<std::string>(&v2) == "this is another long string, and this time we're copying it"); CHECK(*get_if<std::string>(&v3) == ""); // moved-from variant has an empty string now CHECK(*get_if<std::string>(&v4) == "this is another long string, and this time we're copying it"); } TEST_CASE("Equality") { Variant<int, std::string> v1 = std::string("hi"); Variant<int, std::string> v2 = std::string("me"); Variant<int, std::string> v3 = 1; Variant<int, std::string> v4 = 0; Variant<int, std::string> v5; CHECK(v1 == v1); CHECK(v1 != v2); CHECK(v1 != v3); CHECK(v3 != v4); CHECK(v4 == v5); } struct ToStringVisitor { std::string operator()(const std::string& v) { return v; } std::string operator()(int v) { return std::to_string(v); } }; struct IncrementVisitor { void operator()(std::string& v) { v += "1"; } void operator()(int& v) { v += 1; } }; TEST_CASE("Visit") { Variant<std::string, int> v1 = std::string("123"); Variant<std::string, int> v2 = 45; const Variant<std::string, int>& v1c = v1; const Variant<std::string, int>& v2c = v2; // void-returning visitor, const variants std::string r1; visit( [&](const auto& v) { r1 += ToStringVisitor()(v); }, v1c); visit( [&](const auto& v) { r1 += ToStringVisitor()(v); }, v2c); CHECK(r1 == "12345"); // value-returning visitor, const variants std::string r2; r2 += visit(ToStringVisitor(), v1c); r2 += visit(ToStringVisitor(), v2c); CHECK(r2 == "12345"); // void-returning visitor, mutable variant visit(IncrementVisitor(), v1); visit(IncrementVisitor(), v2); CHECK(visit(ToStringVisitor(), v1) == "1231"); CHECK(visit(ToStringVisitor(), v2) == "46"); // value-returning visitor, mutable variant std::string r3; r3 += visit( [&](auto& v) { IncrementVisitor()(v); return ToStringVisitor()(v); }, v1); r3 += visit( [&](auto& v) { IncrementVisitor()(v); return ToStringVisitor()(v); }, v2); CHECK(r3 == "1231147"); } struct MoveOnly { MoveOnly() = default; MoveOnly(const MoveOnly&) = delete; MoveOnly& operator=(const MoveOnly&) = delete; MoveOnly(MoveOnly&&) = default; MoveOnly& operator=(MoveOnly&&) = default; }; TEST_CASE("Move") { Variant<MoveOnly> v1 = MoveOnly{}; Variant<MoveOnly> v2 = std::move(v1); } TEST_CASE("MoveWithCopyableAlternative") { Variant<std::string, MoveOnly> v1 = std::string{"Hello, world! I am longer than a normal hello world string to avoid SSO."}; Variant<std::string, MoveOnly> v2 = std::move(v1); std::string* s1 = get_if<std::string>(&v1); REQUIRE(s1); CHECK(*s1 == ""); std::string* s2 = get_if<std::string>(&v2); REQUIRE(s2); CHECK(*s2 == "Hello, world! I am longer than a normal hello world string to avoid SSO."); } TEST_SUITE_END();