Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Expected<Unit>. #564

Merged
merged 6 commits into from
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions include/vcpkg-test/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,6 @@ namespace vcpkg::Test
PackageSpec emplace(vcpkg::SourceControlFileAndLocation&& scfl);
};

template<class T, class Error>
T&& unwrap(vcpkg::ExpectedT<T, Error>&& p)
{
return std::move(p).value_or_exit(VCPKG_LINE_INFO);
}

template<class T>
T&& unwrap(vcpkg::Optional<T>&& opt)
{
REQUIRE(opt.has_value());
return std::move(*opt.get());
}

inline std::vector<FullPackageSpec> parse_test_fspecs(StringView sv, Triplet t = X86_WINDOWS)
{
std::vector<FullPackageSpec> ret;
Expand All @@ -155,7 +142,7 @@ namespace vcpkg::Test
{
auto opt = parse_qualified_specifier(parser);
REQUIRE(opt.has_value());
ret.push_back(unwrap(opt.get()->to_full_spec(t, ImplicitDefault::YES)));
ret.push_back(opt.get()->to_full_spec(t, ImplicitDefault::YES).value_or_exit(VCPKG_LINE_INFO));
}
return ret;
}
Expand Down
214 changes: 214 additions & 0 deletions include/vcpkg/base/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,220 @@ namespace vcpkg
bool value_is_error;
};

template<class Error>
struct ExpectedT<void, Error>
BillyONeal marked this conversation as resolved.
Show resolved Hide resolved
{
ExpectedT() : value_is_error(false) { }
ExpectedT(ExpectedLeftTag) : value_is_error(false) { }
template<class ConvToError, std::enable_if_t<std::is_convertible_v<ConvToError, Error>, int> = 0>
ExpectedT(ConvToError&& e) : m_error(std::forward<ConvToError>(e)), value_is_error(true)
{
}
template<class ConvToError, std::enable_if_t<std::is_convertible_v<ConvToError, Error>, int> = 0>
ExpectedT(ConvToError&& e, ExpectedRightTag) : m_error(std::forward<ConvToError>(e)), value_is_error(true)
{
}

ExpectedT(const ExpectedT& other) : value_is_error(other.value_is_error)
{
if (value_is_error)
{
::new (&m_error) Error(other.m_error);
}
}

ExpectedT(ExpectedT&& other) : value_is_error(other.value_is_error)
{
if (value_is_error)
{
::new (&m_error) Error(std::move(other.m_error));
}
}

// copy assign is deleted to avoid creating "valueless by exception" states
ExpectedT& operator=(const ExpectedT& other) = delete;

ExpectedT& operator=(ExpectedT&& other) noexcept // enforces termination
{
if (value_is_error)
{
if (other.value_is_error)
{
m_error = std::move(other.m_error);
}
else
{
m_error.~Error();
value_is_error = false;
}
}
else
{
if (other.value_is_error)
{
::new (&m_error) Error(std::move(other.m_error));
value_is_error = true;
}
else
{
// assigning void over void is a no-op :)
}
}

return *this;
}

~ExpectedT()
{
if (value_is_error)
{
m_error.~Error();
}
}

explicit constexpr operator bool() const noexcept { return !value_is_error; }
constexpr bool has_value() const noexcept { return !value_is_error; }

void value_or_exit(const LineInfo& line_info) const { exit_if_error(line_info); }

const Error& error() const&
{
exit_if_not_error();
return m_error;
}

Error&& error() &&
{
exit_if_not_error();
return std::move(m_error);
}

template<class F>
ExpectedT<decltype(std::declval<F&>()()), Error> map(F f) const&
{
if (value_is_error)
{
return {m_error, expected_right_tag};
}
else
{
return {f(), expected_left_tag};
}
}

template<class F>
ExpectedT<decltype(std::declval<F&>()()), Error> map(F f) &&
{
if (value_is_error)
{
return {std::move(m_error), expected_right_tag};
}
else
{
return {f(), expected_left_tag};
}
}

// map_error(F): returns an Expected<T, result_of_calling_F>
//
// If *this holds a value, returns an expected holding the same value.
// Otherwise, returns an expected containing f(error())
template<class F>
ExpectedT<void, decltype(std::declval<F&>()(std::declval<const Error&>()))> map_error(F f) const&
{
if (value_is_error)
{
return {f(m_error), expected_right_tag};
}
else
{
return {};
}
}

template<class F>
ExpectedT<void, decltype(std::declval<F&>()(std::declval<Error>()))> map_error(F f) &&
{
if (value_is_error)
{
return {f(std::move(m_error)), expected_right_tag};
}
else
{
return {};
}
}

// then: f(T, Args...)
// If *this contains a value, returns INVOKE(f, *get(), forward(args)...)
// Otherwise, returns error() put into the same type.
private:
template<class Test>
struct IsThenCompatibleExpected : std::false_type
{
};

template<class OtherTy>
struct IsThenCompatibleExpected<ExpectedT<OtherTy, Error>> : std::true_type
{
};

public:
template<class F, class... Args>
std::invoke_result_t<F, Args...> then(F f, Args&&... args) const&
{
static_assert(IsThenCompatibleExpected<std::invoke_result_t<F, Args...>>::value,
"then expects f to return an expected with the same error type");
if (value_is_error)
{
return {m_error, expected_right_tag};
}
else
{
return std::invoke(f, static_cast<Args&&>(args)...);
}
}

template<class F, class... Args>
std::invoke_result_t<F, Args...> then(F f, Args&&... args) &&
{
static_assert(IsThenCompatibleExpected<std::invoke_result_t<F, Args...>>::value,
"then expects f to return an expected with the same error type");
if (value_is_error)
{
return {m_error, expected_right_tag};
}
else
{
return std::invoke(f, static_cast<Args&&>(args)...);
}
}

private:
void exit_if_error(const LineInfo& line_info) const
{
if (value_is_error)
{
Checks::exit_with_message(line_info, to_string(error()));
}
}

void exit_if_not_error() const noexcept
{
if (!value_is_error)
{
Checks::unreachable(VCPKG_LINE_INFO);
}
}

union
{
Error m_error;
};

bool value_is_error;
};

template<class T>
using ExpectedS = ExpectedT<T, std::string>;
}
2 changes: 2 additions & 0 deletions include/vcpkg/base/fwd/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace vcpkg

template<class T, class Error>
struct ExpectedT;
template<class Error>
struct ExpectedT<void, Error>;

template<class T>
using ExpectedL = ExpectedT<T, LocalizedString>;
Expand Down
2 changes: 1 addition & 1 deletion include/vcpkg/vcpkgpaths.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ namespace vcpkg
// Use {treeish} of "HEAD" for the default branch
ExpectedS<std::string> git_fetch_from_remote_registry(StringView uri, StringView treeish) const;
// runs `git fetch {uri} {treeish}`
Optional<std::string> git_fetch(StringView uri, StringView treeish) const;
ExpectedS<void> git_fetch(StringView uri, StringView treeish) const;
ExpectedS<std::string> git_show_from_remote_registry(StringView hash, const Path& relative_path_to_file) const;
ExpectedS<std::string> git_find_object_id_for_remote_registry_path(StringView hash,
const Path& relative_path_to_file) const;
Expand Down
18 changes: 12 additions & 6 deletions src/vcpkg-test/configparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,40 +428,46 @@ TEST_CASE ("AssetConfigParser azurl provider", "[assetconfigparser]")
CHECK(empty.m_read_headers.empty());
}
{
DownloadManagerConfig dm = Test::unwrap(parse_download_configuration("x-azurl,https://abc/123,foo"));
DownloadManagerConfig dm =
parse_download_configuration("x-azurl,https://abc/123,foo").value_or_exit(VCPKG_LINE_INFO);
CHECK(dm.m_read_url_template == "https://abc/123/<SHA>?foo");
CHECK(dm.m_read_headers.empty());
CHECK(dm.m_write_url_template == nullopt);
}
{
DownloadManagerConfig dm = Test::unwrap(parse_download_configuration("x-azurl,https://abc/123/,foo"));
DownloadManagerConfig dm =
parse_download_configuration("x-azurl,https://abc/123/,foo").value_or_exit(VCPKG_LINE_INFO);
CHECK(dm.m_read_url_template == "https://abc/123/<SHA>?foo");
CHECK(dm.m_read_headers.empty());
CHECK(dm.m_write_url_template == nullopt);
CHECK(dm.m_secrets == std::vector<std::string>{"foo"});
}
{
DownloadManagerConfig dm = Test::unwrap(parse_download_configuration("x-azurl,https://abc/123,?foo"));
DownloadManagerConfig dm =
parse_download_configuration("x-azurl,https://abc/123,?foo").value_or_exit(VCPKG_LINE_INFO);
CHECK(dm.m_read_url_template == "https://abc/123/<SHA>?foo");
CHECK(dm.m_read_headers.empty());
CHECK(dm.m_write_url_template == nullopt);
CHECK(dm.m_secrets == std::vector<std::string>{"?foo"});
}
{
DownloadManagerConfig dm = Test::unwrap(parse_download_configuration("x-azurl,https://abc/123"));
DownloadManagerConfig dm =
parse_download_configuration("x-azurl,https://abc/123").value_or_exit(VCPKG_LINE_INFO);
CHECK(dm.m_read_url_template == "https://abc/123/<SHA>");
CHECK(dm.m_read_headers.empty());
CHECK(dm.m_write_url_template == nullopt);
}
{
DownloadManagerConfig dm = Test::unwrap(parse_download_configuration("x-azurl,https://abc/123,,readwrite"));
DownloadManagerConfig dm =
parse_download_configuration("x-azurl,https://abc/123,,readwrite").value_or_exit(VCPKG_LINE_INFO);
CHECK(dm.m_read_url_template == "https://abc/123/<SHA>");
CHECK(dm.m_read_headers.empty());
CHECK(dm.m_write_url_template == "https://abc/123/<SHA>");
Test::check_ranges(dm.m_write_headers, azure_blob_headers());
}
{
DownloadManagerConfig dm = Test::unwrap(parse_download_configuration("x-azurl,https://abc/123,foo,readwrite"));
DownloadManagerConfig dm =
parse_download_configuration("x-azurl,https://abc/123,foo,readwrite").value_or_exit(VCPKG_LINE_INFO);
CHECK(dm.m_read_url_template == "https://abc/123/<SHA>?foo");
CHECK(dm.m_read_headers.empty());
CHECK(dm.m_write_url_template == "https://abc/123/<SHA>?foo");
Expand Down
Loading