diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj index 2198df7d3..111fa7075 100644 --- a/fish.xcodeproj/project.pbxproj +++ b/fish.xcodeproj/project.pbxproj @@ -676,6 +676,7 @@ D03238891849D1980032CF2C /* pager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pager.cpp; sourceTree = ""; }; D032388A1849D1980032CF2C /* pager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pager.h; sourceTree = ""; }; D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = ""; }; + D043012D1F5350E400942A50 /* maybe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = maybe.h; sourceTree = ""; }; D04F7F8D1BA4DCD900B0F227 /* pcre2_compile.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcre2_compile.c; sourceTree = ""; }; D04F7F901BA4DCE900B0F227 /* pcre2_config.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcre2_config.c; sourceTree = ""; }; D04F7F931BA4DCFA00B0F227 /* pcre2_context.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcre2_context.c; sourceTree = ""; }; @@ -1206,6 +1207,7 @@ D03EE83814DF88B200FC7150 /* lru.h */, D0A0851A13B3ACEE0099B651 /* output.h */, D0A0855113B3ACEE0099B651 /* output.cpp */, + D043012D1F5350E400942A50 /* maybe.h */, D032388A1849D1980032CF2C /* pager.h */, D03238891849D1980032CF2C /* pager.cpp */, D0A0851B13B3ACEE0099B651 /* parse_util.h */, diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 438ec2d48..1817533f1 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -52,6 +52,7 @@ #include "io.h" #include "iothread.h" #include "lru.h" +#include "maybe.h" #include "pager.h" #include "parse_constants.h" #include "parse_tree.h" @@ -4247,6 +4248,37 @@ static void test_illegal_command_exit_code(void) { popd(); } +void test_maybe() { + say(L"Testing maybe_t"); + do_test(! bool(maybe_t())); + maybe_t m(3); + do_test(m.has_value()); + do_test(m.value() == 3); + m.reset(); + do_test(!m.has_value()); + m = 123; + do_test(m.has_value()); + do_test(m.has_value() && m.value() == 123); + m = maybe_t(); + do_test(!m.has_value()); + m = maybe_t(64); + do_test(m.has_value() && m.value() == 64); + m = 123; + do_test(m == maybe_t(123)); + do_test(m != maybe_t()); + do_test(maybe_t() == none()); + do_test(!maybe_t(none()).has_value()); + m = none(); + do_test(! bool(m)); + + maybe_t m2("abc"); + do_test(!m2.missing_or_empty()); + m2 = ""; + do_test(m2.missing_or_empty()); + m2 = none(); + do_test(m2.missing_or_empty()); +} + /// Main test. int main(int argc, char **argv) { UNUSED(argc); @@ -4342,6 +4374,7 @@ int main(int argc, char **argv) { if (should_test_function("history_formats")) history_tests_t::test_history_formats(); if (should_test_function("string")) test_string(); if (should_test_function("illegal_command_exit_code")) test_illegal_command_exit_code(); + if (should_test_function("maybe")) test_maybe(); // history_tests_t::test_history_speed(); say(L"Encountered %d errors in low-level tests", err_count); diff --git a/src/maybe.h b/src/maybe.h new file mode 100644 index 000000000..ac493f350 --- /dev/null +++ b/src/maybe.h @@ -0,0 +1,112 @@ +#ifndef FISH_MAYBE_H +#define FISH_MAYBE_H + +#include +#include + +// A none_t is a helper type used to implicitly initialize maybe_t. +// Example usage: +// maybe_t sqrt(int x) { +// if (x < 0) return none(); +// return (int)sqrt(x); +// } +enum class none_t { none = 1 }; +inline constexpr none_t none() { return none_t::none; } + +// Support for a maybe, also known as Optional. +// This is a value-type class that stores a value of T in aligned storage. +template +class maybe_t { + alignas(T) char storage[sizeof(T)]; + bool filled = false; + + public: + // return whether the receiver contains a value. + bool has_value() const { return filled; } + + // bool conversion indicates whether the receiver contains a value. + explicit operator bool() const { return filled; } + + // The default constructor constructs a maybe with no value. + maybe_t() {} + + // Construct a maybe_t from a none_t + /* implicit */ maybe_t(none_t n) { (void)n; } + + // Construct a maybe_t from a value T. + /* implicit */ maybe_t(T &&v) : filled(true) { new (storage) T(std::forward(v)); } + + // Construct a maybe_t from a value T. + /* implicit */ maybe_t(const T &v) : filled(true) { new (storage) T(v); } + + // Move constructor. + /* implicit */ maybe_t(maybe_t &&v) { + if (v.filled) { + *this = std::move(v.value()); + } + } + + // Access the value. + T &value() { + assert(filled && "maybe_t does not have a value"); + return *reinterpret_cast(storage); + } + + const T &value() const { + assert(filled && "maybe_t does not have a value"); + return *reinterpret_cast(storage); + } + + // Clear the value. + void reset() { + if (filled) { + value().~T(); + filled = false; + } + } + + // Assign a new value. + maybe_t &operator=(T &&v) { + if (filled) { + value() = std::move(v); + } else { + new (storage) T(std::move(v)); + filled = true; + } + return *this; + } + + maybe_t &operator=(maybe_t &&v) { + if (!v) { + reset(); + } else { + *this = std::move(*v); + } + return *this; + } + + // Dereference support. + const T *operator->() const { return &value(); } + T *operator->() { return &value(); } + const T &operator*() const { return value(); } + T &operator*() { return value(); } + + // Helper to replace missing_or_empty() on env_var_t. + // Uses SFINAE to only introduce this function if T has an empty() type. + template + decltype(S().empty(), bool()) missing_or_empty() const { + return ! has_value() || value().empty(); + } + + // Compare values for equality. + bool operator==(const maybe_t &rhs) const { + if (this->has_value() && rhs.has_value()) return this->value() == rhs.value(); + return this->has_value() == rhs.has_value(); + } + + bool operator!=(const maybe_t &rhs) const { return !(*this == rhs); } + + ~maybe_t() { reset(); } +}; + +#endif