// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once #include "Luau/Common.h" #include #include #include #include #include namespace Luau { template class Variant { static_assert(sizeof...(Ts) > 0, "variant must have at least 1 type (empty variants are ill-formed)"); static_assert(std::disjunction_v...> == false, "variant does not allow void as an alternative type"); static_assert(std::disjunction_v...> == false, "variant does not allow references as an alternative type"); static_assert(std::disjunction_v...> == false, "variant does not allow arrays as an alternative type"); private: template static constexpr int getTypeId() { using TT = std::decay_t; constexpr int N = sizeof...(Ts); constexpr bool is[N] = {std::is_same_v...}; for (int i = 0; i < N; ++i) if (is[i]) return i; return -1; } template struct First { using type = T; }; public: using first_alternative = typename First::type; Variant() { static_assert(std::is_default_constructible_v, "first alternative type must be default constructible"); typeId = 0; new (&storage) first_alternative(); } template Variant(T&& value, std::enable_if_t() >= 0>* = 0) { using TT = std::decay_t; constexpr int tid = getTypeId(); typeId = tid; new (&storage) TT(value); } Variant(const Variant& other) { typeId = other.typeId; tableCopy[typeId](&storage, &other.storage); } Variant(Variant&& other) { typeId = other.typeId; tableMove[typeId](&storage, &other.storage); } ~Variant() { tableDtor[typeId](&storage); } Variant& operator=(const Variant& other) { Variant copy(other); // static_cast is equivalent to std::move() but faster in Debug return *this = static_cast(copy); } Variant& operator=(Variant&& other) { if (this != &other) { tableDtor[typeId](&storage); typeId = other.typeId; tableMove[typeId](&storage, &other.storage); // nothrow } return *this; } template T& emplace(Args&&... args) { using TT = std::decay_t; constexpr int tid = getTypeId(); static_assert(tid >= 0, "unsupported T"); tableDtor[typeId](&storage); typeId = tid; new (&storage) TT(std::forward(args)...); return *reinterpret_cast(&storage); } template const T* get_if() const { constexpr int tid = getTypeId(); static_assert(tid >= 0, "unsupported T"); return tid == typeId ? reinterpret_cast(&storage) : nullptr; } template T* get_if() { constexpr int tid = getTypeId(); static_assert(tid >= 0, "unsupported T"); return tid == typeId ? reinterpret_cast(&storage) : nullptr; } bool valueless_by_exception() const { return false; } int index() const { return typeId; } bool operator==(const Variant& other) const { static constexpr FnPred table[sizeof...(Ts)] = {&fnPredEq...}; return typeId == other.typeId && table[typeId](&storage, &other.storage); } bool operator!=(const Variant& other) const { return !(*this == other); } private: static constexpr size_t cmax(std::initializer_list l) { size_t res = 0; for (size_t i : l) res = (res < i) ? i : res; return res; } static constexpr size_t storageSize = cmax({sizeof(Ts)...}); static constexpr size_t storageAlign = cmax({alignof(Ts)...}); using FnCopy = void (*)(void*, const void*); using FnMove = void (*)(void*, void*); using FnDtor = void (*)(void*); using FnPred = bool (*)(const void*, const void*); template static void fnCopy(void* dst, const void* src) { new (dst) T(*static_cast(src)); } template static void fnMove(void* dst, void* src) { // static_cast is equivalent to std::move() but faster in Debug new (dst) T(static_cast(*static_cast(src))); } template static void fnDtor(void* dst) { static_cast(dst)->~T(); } template static bool fnPredEq(const void* lhs, const void* rhs) { return *static_cast(lhs) == *static_cast(rhs); } static constexpr FnCopy tableCopy[sizeof...(Ts)] = {&fnCopy...}; static constexpr FnMove tableMove[sizeof...(Ts)] = {&fnMove...}; static constexpr FnDtor tableDtor[sizeof...(Ts)] = {&fnDtor...}; int typeId; alignas(storageAlign) char storage[storageSize]; template friend auto visit(Visitor&& vis, const Variant<_Ts...>& var); template friend auto visit(Visitor&& vis, Variant<_Ts...>& var); }; template const T* get_if(const Variant* var) { return var ? var->template get_if() : nullptr; } template T* get_if(Variant* var) { return var ? var->template get_if() : nullptr; } template static void fnVisitR(Visitor& vis, Result& dst, std::conditional_t, const void, void>* src) { dst = vis(*static_cast(src)); } template static void fnVisitV(Visitor& vis, std::conditional_t, const void, void>* src) { vis(*static_cast(src)); } template auto visit(Visitor&& vis, const Variant& var) { static_assert(std::conjunction_v...>, "visitor must accept every alternative as an argument"); using Result = std::invoke_result_t::first_alternative>; static_assert(std::conjunction_v>...>, "visitor result type must be consistent between alternatives"); if constexpr (std::is_same_v) { using FnVisitV = void (*)(Visitor&, const void*); static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV...}; tableVisit[var.typeId](vis, &var.storage); } else { using FnVisitR = void (*)(Visitor&, Result&, const void*); static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR...}; Result res; tableVisit[var.typeId](vis, res, &var.storage); return res; } } template auto visit(Visitor&& vis, Variant& var) { static_assert(std::conjunction_v...>, "visitor must accept every alternative as an argument"); using Result = std::invoke_result_t::first_alternative&>; static_assert(std::conjunction_v>...>, "visitor result type must be consistent between alternatives"); if constexpr (std::is_same_v) { using FnVisitV = void (*)(Visitor&, void*); static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV...}; tableVisit[var.typeId](vis, &var.storage); } else { using FnVisitR = void (*)(Visitor&, Result&, void*); static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR...}; Result res; tableVisit[var.typeId](vis, res, &var.storage); return res; } } template inline constexpr bool always_false_v = false; } // namespace Luau