diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index a9617a516..ca1bd1f35 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -115,6 +115,7 @@ target_sources(subspace PUBLIC "iter/iterator.h" "iter/iterator_concept.h" "iter/iterator_defn.h" + "iter/iterator_impl.h" "iter/iterator_ref.h" "iter/once.h" "iter/product.h" @@ -123,6 +124,7 @@ target_sources(subspace PUBLIC "iter/size_hint.h" "iter/size_hint_impl.h" "iter/successors.h" + "iter/try_from_iterator.h" "iter/zip.h" "macros/__private/compiler_bugs.h" "macros/assume.h" diff --git a/sus/iter/from_iterator.h b/sus/iter/from_iterator.h index 7bf39c979..1712b8649 100644 --- a/sus/iter/from_iterator.h +++ b/sus/iter/from_iterator.h @@ -50,9 +50,10 @@ concept FromIterator = /// typically called through calling `collect()` on an iterator. However this /// function can be preferrable for some readers, especially in generic template /// code. -template - requires( - FromIterator::Item>) +template + requires(FromIterator::Item> && // + ::sus::mem::IsMoveRef) constexpr inline ToType from_iter(IntoIter&& into_iter) noexcept { return FromIteratorImpl::from_iter( ::sus::move(into_iter).into_iter()); diff --git a/sus/iter/iterator.h b/sus/iter/iterator.h index 0692fb50f..ffb5113d9 100644 --- a/sus/iter/iterator.h +++ b/sus/iter/iterator.h @@ -16,6 +16,7 @@ // IWYU pragma: begin_exports #include "sus/iter/iterator_defn.h" +#include "sus/iter/iterator_impl.h" // The usize formatting is in unsigned_integer_impl.h which has an include cycle // with usize->Array->Array iterators->SizeHint->usize. So the SizeHint diff --git a/sus/iter/iterator_defn.h b/sus/iter/iterator_defn.h index 622a2da58..8daefc906 100644 --- a/sus/iter/iterator_defn.h +++ b/sus/iter/iterator_defn.h @@ -927,6 +927,57 @@ class IteratorBase { ::sus::fn::FnMut&)> auto pred) && noexcept; + /// Fallibly transforms an iterator into a collection, short circuiting if a + /// failure is encountered. + /// + /// `try_collect()` is a variation of `collect()` that allows fallible + /// conversions during collection. Its main use case is simplifying + /// conversions from iterators yielding `Option` into + /// `Option>`, or similarly for other Try types (e.g. `Result` + /// or `std::optional`). + /// + /// Importantly, `try_collect()` doesn’t require that the outer `Try` type + /// also implements FromIterator; only the `Try` type's `Output` type must + /// implement it. Concretely, this means that collecting into + /// `TryThing, _>` can be valid because `Vec` implements + /// FromIterator, even if `TryThing` doesn’t. + /// + /// Also, if a failure is encountered during `try_collect()`, the iterator is + /// still valid and may continue to be used, in which case it will continue + /// iterating starting after the element that triggered the failure. See the + /// last example below for an example of how this works. + /// + /// # Examples + /// Successfully collecting an iterator of `Option` into + /// `Option>`: + /// ``` + /// auto u = Vec>::with(some(1), some(2), some(3)); + /// auto v = sus::move(u).into_iter().try_collect>(); + /// sus::check(v == some(Vec::with(1, 2, 3 ))); + /// ``` + /// Failing to collect in the same way: + /// ``` + /// auto u = Vec>::with(some(1), some(2), none(), some(3)); + /// auto v = sus::move(u).into_iter().try_collect>(); + /// sus::check(v == none()); + /// ``` + /// A similar example, but with [`Result`](sus::result::Result): + /// ``` + /// enum Error { ERROR }; + /// auto u = Vec>::with(ok(1), ok(2), ok(3)); + /// auto v = sus::move(u).into_iter().try_collect>(); + /// sus::check(v == ok(Vec::with(1, 2, 3))); + /// auto w = Vec>::with(ok(1), ok(2), err(ERROR), ok(3)); + /// auto x = sus::move(w).into_iter().try_collect>(); + /// sus::check(x == err(ERROR)); + /// ``` + template + requires(::sus::ops::Try && // + FromIterator> && + // Void can not be collected from. + !std::is_void_v<::sus::ops::TryOutputType>) + constexpr auto try_collect() noexcept; + /// This function acts like `fold()` but the closure returns a type that /// satisfies `sus::ops::Try` and which converts to the accumulator type on /// success through the Try concept. If the closure ever returns failure, the @@ -1059,7 +1110,7 @@ class IteratorBase { /// sus::move(iter).collect>() /// ``` template C> - constexpr FromIterator auto collect() && noexcept; + constexpr C collect() && noexcept; /// Transforms an iterator into a Vec. /// @@ -1433,7 +1484,8 @@ template template < ::sus::fn::FnMut<::sus::fn::NonVoid(const std::remove_reference_t&)> KeyFn, - int&..., class Key> + int&..., + class Key> requires(::sus::ops::Ord && // !std::is_reference_v) constexpr Option IteratorBase::max_by_key( @@ -1490,7 +1542,8 @@ template template < ::sus::fn::FnMut<::sus::fn::NonVoid(const std::remove_reference_t&)> KeyFn, - int&..., class Key> + int&..., + class Key> requires(::sus::ops::Ord && // !std::is_reference_v) constexpr Option IteratorBase::min_by_key( @@ -1699,7 +1752,8 @@ constexpr Option IteratorBase::rposition( template template F, - int&..., class R, class InnerR> + int&..., class R, + class InnerR> requires(::sus::option::__private::IsOptionType::value && // !std::is_reference_v) constexpr Iterator auto IteratorBase::scan( @@ -1859,14 +1913,13 @@ constexpr std::weak_ordering IteratorBase::weak_cmp_by( template template C> -constexpr FromIterator auto -IteratorBase::collect() && noexcept { +constexpr C IteratorBase::collect() && noexcept { return from_iter(static_cast(*this)); } template -::sus::containers::Vec constexpr IteratorBase< - Iter, Item>::collect_vec() && noexcept { +constexpr ::sus::containers::Vec +IteratorBase::collect_vec() && noexcept { return from_iter<::sus::containers::Vec>(static_cast(*this)); } diff --git a/sus/iter/iterator_impl.h b/sus/iter/iterator_impl.h new file mode 100644 index 000000000..211c48cd2 --- /dev/null +++ b/sus/iter/iterator_impl.h @@ -0,0 +1,38 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// IWYU pragma: private, include "sus/iter/iterator.h" +// IWYU pragma: friend "sus/.*" +#pragma once + +#include + +#include "sus/iter/try_from_iterator.h" + +namespace sus::iter { + +// Implementation of IteratorBase::try_collect has to live here to avoid +// cyclical includes, as the impl of try_from_iter() needs to see IteratorBase +// in order to make something that can be consumed by FromIterator. +template +template + requires(::sus::ops::Try && // + FromIterator> && + // Void can not be collected from. + !std::is_void_v<::sus::ops::TryOutputType>) +constexpr auto IteratorBase::try_collect() noexcept { + return try_from_iter(static_cast(*this)); +} + +} // namespace sus::iter diff --git a/sus/iter/iterator_unittest.cc b/sus/iter/iterator_unittest.cc index f0e5fb7c8..a87641d0d 100644 --- a/sus/iter/iterator_unittest.cc +++ b/sus/iter/iterator_unittest.cc @@ -33,10 +33,13 @@ #include "sus/num/overflow_integer.h" #include "sus/ops/eq.h" #include "sus/prelude.h" +#include "sus/test/no_copy_move.h" using sus::containers::Array; using sus::iter::IteratorBase; using sus::option::Option; +using sus::result::Result; +using sus::test::NoCopyMove; namespace sus::test::iter { @@ -45,6 +48,10 @@ struct CollectSum { T sum; }; +struct CollectRefs { + sus::Vec vec; +}; + } // namespace sus::test::iter template @@ -57,6 +64,16 @@ struct sus::iter::FromIteratorImpl> { } }; +template <> +struct sus::iter::FromIteratorImpl { + static sus::test::iter::CollectRefs from_iter( + sus::iter::IntoIterator auto iter) noexcept { + auto v = sus::Vec(); + for (const NoCopyMove& t : sus::move(iter).into_iter()) v.push(&t); + return sus::test::iter::CollectRefs(sus::move(v)); + } +}; + namespace { using namespace sus::test::iter; @@ -460,6 +477,10 @@ TEST(Iterator, Collect) { ArrayIterator::with_array(nums).collect>(); EXPECT_EQ(collected.sum, 1 + 2 + 3 + 4 + 5); + auto from = sus::iter::from_iter>( + ArrayIterator::with_array(nums)); + EXPECT_EQ(from.sum, 1 + 2 + 3 + 4 + 5); + static_assert(sus::Array::with(1, 2, 3, 4, 5) .into_iter() .collect>() @@ -481,6 +502,97 @@ TEST(Iterator, CollectVec) { sus::Vec::with(1, 2, 3, 4, 5)); } +TEST(Iterator, TryCollect) { + // Option. + { + auto collected = sus::Array, 3>::with( + ::sus::some(1), ::sus::some(2), ::sus::some(3)) + .into_iter() + .try_collect>(); + static_assert(std::same_as>>); + EXPECT_EQ(collected.is_some(), true); + EXPECT_EQ(collected.as_value(), sus::Vec::with(1, 2, 3)); + + auto it = sus::Array, 3>::with(::sus::some(1), ::sus::none(), + ::sus::some(3)) + .into_iter(); + auto up_to_none = it.try_collect>(); + EXPECT_EQ(up_to_none, sus::none()); + auto after_none = it.try_collect>(); + EXPECT_EQ(after_none.as_value(), sus::Vec::with(3)); + + NoCopyMove n[3]; + auto refs = sus::Array, 3>::with( + ::sus::some(n[0]), ::sus::some(n[1]), ::sus::some(n[2])) + .into_iter() + .try_collect(); + EXPECT_EQ(refs.is_some(), true); + EXPECT_EQ(refs.as_value().vec[0u], &n[0]); + EXPECT_EQ(refs.as_value().vec[1u], &n[1]); + EXPECT_EQ(refs.as_value().vec[2u], &n[2]); + } + // Result. + enum Error { ERROR }; + { + auto collected = sus::Array, 3>::with( + ::sus::ok(1), ::sus::ok(2), ::sus::ok(3)) + .into_iter() + .try_collect>(); + static_assert(std::same_as, Error>>); + EXPECT_EQ(collected.as_ok(), sus::Vec::with(1, 2, 3)); + + auto it = sus::Array, 3>::with( + ::sus::ok(1), ::sus::err(ERROR), ::sus::ok(3)) + .into_iter(); + auto up_to_err = it.try_collect>(); + EXPECT_EQ(up_to_err, sus::err(ERROR)); + auto after_err = it.try_collect>(); + EXPECT_EQ(after_err.as_ok(), sus::Vec::with(3)); + } + + auto from = + sus::iter::try_from_iter>(sus::Array, 3>::with( + ::sus::some(1), ::sus::some(2), ::sus::some(3))); + EXPECT_EQ(from.as_value(), sus::Vec::with(1, 2, 3)); + + static_assert( + sus::Array, 3>::with(sus::some(1), sus::some(2), sus::some(3)) + .into_iter() + .try_collect>() + .unwrap() + .into_iter() + .sum() == 1 + 2 + 3); +} + +TEST(Iterator, TryCollect_Example) { + using sus::none; + using sus::ok; + using sus::err; + using sus::Option; + using sus::result::Result; + using sus::some; + using sus::Vec; + { + auto u = Vec>::with(some(1), some(2), some(3)); + auto v = sus::move(u).into_iter().try_collect>(); + sus::check(v == some(Vec::with(1, 2, 3))); + } + { + auto u = Vec>::with(some(1), some(2), none(), some(3)); + auto v = sus::move(u).into_iter().try_collect>(); + sus::check(v == none()); + } + { + enum Error { ERROR }; + auto u = Vec>::with(ok(1), ok(2), ok(3)); + auto v = sus::move(u).into_iter().try_collect>(); + sus::check(v == ok(Vec::with(1, 2, 3))); + auto w = Vec>::with(ok(1), ok(2), err(ERROR), ok(3)); + auto x = sus::move(w).into_iter().try_collect>(); + sus::check(x == err(ERROR)); + } +} + TEST(Iterator, Rev) { i32 nums[5] = {1, 2, 3, 4, 5}; diff --git a/sus/iter/try_from_iterator.h b/sus/iter/try_from_iterator.h new file mode 100644 index 000000000..fbddb9398 --- /dev/null +++ b/sus/iter/try_from_iterator.h @@ -0,0 +1,100 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "sus/iter/from_iterator.h" +#include "sus/iter/into_iterator.h" +#include "sus/iter/iterator_defn.h" +#include "sus/mem/move.h" +#include "sus/ops/try.h" +#include "sus/option/option.h" + +namespace sus::iter { + +namespace __private { + +template +struct TryFromIteratorUnwrapper final + : public IteratorBase< + TryFromIteratorUnwrapper, + ::sus::ops::TryOutputType> { + using FromItem = typename SourceIter::Item; + using Item = ::sus::ops::TryOutputType; + + constexpr TryFromIteratorUnwrapper(SourceIter& iter, + ::sus::Option& failure) + : iter(iter), failure(failure) {} + + constexpr ::sus::Option next() noexcept { + ::sus::option::Option input = iter.next(); + if (input.is_some()) { + if (::sus::ops::try_is_success(input.as_value())) { + return ::sus::move(input).map(&::sus::ops::try_into_output); + } else { + failure = ::sus::move(input); + } + } + return ::sus::Option(); + } + + constexpr SizeHint size_hint() const noexcept { + return SizeHint(0u, iter.size_hint().upper); + } + + SourceIter& iter; + ::sus::Option& failure; +}; + +} // namespace __private + +/// Constructs `ToType` from a type that can be turned into an `Iterator` over +/// elements of type `ItemType`. +/// +/// If a failure value is seen in the iterator, then the failure value will be +/// returned. Otherwise, the `ToType` success type (`TryOutputType`) is +/// constructed from the success values in the Iterator, and a +/// success-representing `ToType` is returned containing that success type. +/// +/// This is the other end of +/// [`Iterator::try_collect()`](::sus::iter::IteratorBase::try_collect), and is +/// typically called through calling `try_collect()` on an iterator. However +/// this function can be preferrable for some readers, especially in generic +/// template code. +template ::Item, + class ToType = ::sus::ops::TryRemapOutputType> + requires(::sus::mem::IsMoveRef && // + ::sus::ops::Try && // + FromIterator> && + // Void can not be collected from. + !std::is_void_v<::sus::ops::TryOutputType>) +constexpr inline ToType try_from_iter(IntoIter&& into_iter) noexcept { + // This is supposed to be guaranteed by TryRemapOutputType. + static_assert(::sus::ops::TryErrorConvertibleTo); + + auto&& iter = ::sus::move(into_iter).into_iter(); + using SourceIter = std::remove_reference_t; + + ::sus::Option failure; + auto out = ::sus::ops::try_from_output(from_iter( + __private::TryFromIteratorUnwrapper(iter, failure))); + if (failure.is_some()) + out = ::sus::ops::try_preserve_error(::sus::move(failure).unwrap()); + return out; +} + +} // namespace sus::iter diff --git a/sus/macros/__private/compiler_bugs.h b/sus/macros/__private/compiler_bugs.h index c82503a5a..16a190520 100644 --- a/sus/macros/__private/compiler_bugs.h +++ b/sus/macros/__private/compiler_bugs.h @@ -136,3 +136,14 @@ #define sus_gcc_bug_110905(...) #define sus_gcc_bug_110905_else(...) __VA_ARGS__ #endif + +// TODO: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110927 +// GCC does not parse dependent types in a partial specialization. +#if defined(__GNUC__) && __GNUC__ > 0 // TODO: Update when the bug is fixed. +#define sus_gcc_bug_110927(...) __VA_ARGS__ +#define sus_gcc_bug_110927_else(...) +#define sus_gcc_bug_110927_exists +#else +#define sus_gcc_bug_110927(...) +#define sus_gcc_bug_110927_else(...) __VA_ARGS__ +#endif diff --git a/sus/ops/try.h b/sus/ops/try.h index ece14754a..22bf3fc3d 100644 --- a/sus/ops/try.h +++ b/sus/ops/try.h @@ -14,6 +14,8 @@ #pragma once +#include "sus/macros/__private/compiler_bugs.h" +#include "sus/mem/forward.h" #include "sus/mem/move.h" namespace sus::ops { @@ -43,10 +45,13 @@ concept Try = requires { // The Try type is not a reference, conversions require concrete types. requires !std::is_reference_v; - // The Output type can be converted to/from the Try type. + // The Output success type can be converted to/from the Try type. typename TryImpl::Output; - // The Output type is also not a reference. - requires !std::is_reference_v::Output>; +#if !defined(sus_gcc_bug_110927_exists) + // The Try type can be used to produce another Try type with a different + // Output type but the same error type. + typename TryImpl::template RemapOutput::Output>; +#endif } // && // requires(const T& t, T&& tt) { @@ -64,9 +69,12 @@ concept Try = // construct a `T` with a void Output type, require `TryDefault` instead and // use `from_default()`. (std::is_void_v::Output> || // - requires(typename TryImpl::Output output) { + requires(typename TryImpl::Output&& output) { // from_output() converts a success type to the Try type. - { TryImpl::from_output(::sus::move(output)) } -> std::same_as; + { + TryImpl::from_output( + ::sus::forward::Output>(output)) + } -> std::same_as; }); /// Identifies Try types which can be constructed with a default success value. @@ -82,6 +90,32 @@ concept TryDefault = Try && requires { { TryImpl::from_default() } -> std::same_as; }; +/// Can be used to further constrain the relationship between two `Try` types +/// such that an error in one can be used to construct the other type. +/// +/// This allows `Try` to be returned from a function working with +/// `Try` in the case of an error, as `sus::ops::try_preserve_error()` +/// can be used to construct the error return type. +template +concept TryErrorConvertibleTo = + Try && // + Try && // + requires(From&& f) { + // preserve_error() constructs a Try type from another related type while + // passing the error state along. + { + TryImpl::template preserve_error(::sus::move(f)) + } -> std::same_as; + }; + +/// A helper to get the `Output` type for a type `T` that satisfies `Try`. +template +using TryOutputType = typename TryImpl::Output; + +/// A helper to get the `RemapOutput` type for a type `T` that satisfies `Try`. +template +using TryRemapOutputType = typename TryImpl::template RemapOutput; + /// Determines if a type `T` that satisfies `Try` represents success in its /// current state. template @@ -123,7 +157,8 @@ constexpr inline TryImpl::Output try_into_output(T&& t) noexcept { template requires(!std::is_void_v::Output>) constexpr inline T try_from_output(typename TryImpl::Output&& t) noexcept { - return TryImpl::from_output(::sus::move(t)); + return TryImpl::from_output( + ::sus::forward::Output>(t)); } /// Constructs an object of type `T` that satisfies `TryDefault` (and `Try`) @@ -140,4 +175,19 @@ constexpr inline T try_from_default() noexcept { return TryImpl::from_default(); } +/// Converts from a `Try` type `T` to another `Try` type `U` with a compatible +/// error state. The input must be in an error state, and the output will be as +/// well. +/// +/// # Panics +/// +/// If the input is not in an error state (`is_success()` would return false) +/// the function will panic. +template T> + requires(::sus::mem::IsMoveRef) +constexpr inline U try_preserve_error(T&& t) noexcept { + ::sus::check(!TryImpl::is_success(t)); + return TryImpl::preserve_error(::sus::move(t)); +} + } // namespace sus::ops diff --git a/sus/option/compat_option.h b/sus/option/compat_option.h index 503134aad..f6ca191b9 100644 --- a/sus/option/compat_option.h +++ b/sus/option/compat_option.h @@ -138,6 +138,8 @@ constexpr Option::operator std::optional*>() template struct sus::ops::TryImpl> { using Output = T; + template + using RemapOutput = std::optional; constexpr static bool is_success(const std::optional& t) { return t.has_value(); } @@ -150,6 +152,13 @@ struct sus::ops::TryImpl> { constexpr static std::optional from_output(Output t) { return std::optional(std::in_place, ::sus::move(t)); } + template + constexpr static std::optional preserve_error(std::optional) noexcept { + // The incoming optional is known to be empty (the error state) and this is + // checked by try_preserve_error() before coming here. So we can just return + // another empty optional. + return std::optional(); + } // Implements sus::ops::TryDefault for `std::optional` if `T` satisfies // `Default`. diff --git a/sus/option/option.h b/sus/option/option.h index 1fc0136ba..3294faf96 100644 --- a/sus/option/option.h +++ b/sus/option/option.h @@ -1489,6 +1489,8 @@ sus_pure_const inline constexpr auto none() noexcept { template struct sus::ops::TryImpl<::sus::option::Option> { using Output = T; + template + using RemapOutput = ::sus::option::Option; constexpr static bool is_success(const ::sus::option::Option& t) noexcept { return t.is_some(); } @@ -1500,6 +1502,14 @@ struct sus::ops::TryImpl<::sus::option::Option> { constexpr static ::sus::option::Option from_output(Output t) noexcept { return ::sus::option::Option::with(::sus::move(t)); } + template + constexpr static ::sus::option::Option preserve_error( + ::sus::option::Option) noexcept { + // The incoming Option is known to be empty (the error state) and this is + // checked by try_preserve_error() before coming here. So we can just return + // another empty Option. + return ::sus::option::Option(); + } // Implements sus::ops::TryDefault for `Option` if `T` satisfies `Default`. constexpr static ::sus::option::Option from_default() noexcept @@ -1677,16 +1687,16 @@ struct sus::iter::FromIteratorImpl<::sus::option::Option> { // until it reaches a `None` or the end. struct UntilNoneIter final : public ::sus::iter::IteratorBase { - UntilNoneIter(Iter&& iter, bool& found_none) + constexpr UntilNoneIter(Iter&& iter, bool& found_none) : iter(iter), found_none(found_none) {} - ::sus::option::Option next() noexcept { + constexpr ::sus::option::Option next() noexcept { ::sus::option::Option<::sus::option::Option> item = iter.next(); if (found_none || item.is_none()) return ::sus::option::Option(); found_none = item->is_none(); return ::sus::move(item).flatten(); } - ::sus::iter::SizeHint size_hint() const noexcept { + constexpr ::sus::iter::SizeHint size_hint() const noexcept { return ::sus::iter::SizeHint(0u, iter.size_hint().upper); } @@ -1696,10 +1706,9 @@ struct sus::iter::FromIteratorImpl<::sus::option::Option> { bool found_none = false; auto iter = UntilNoneIter(::sus::move(option_iter).into_iter(), found_none); - auto collected = sus::iter::from_iter(::sus::move(iter)); - if (found_none) - return ::sus::option::Option(); - else - return ::sus::option::Option::with(::sus::move(collected)); + auto collected = + Option::with(sus::iter::from_iter(::sus::move(iter))); + if (found_none) collected = ::sus::option::Option(); + return collected; } }; diff --git a/sus/result/result.h b/sus/result/result.h index 2f548db56..477b195bb 100644 --- a/sus/result/result.h +++ b/sus/result/result.h @@ -1140,17 +1140,27 @@ template requires(!std::is_void_v) struct sus::ops::TryImpl<::sus::result::Result> { using Output = T; + template + using RemapOutput = ::sus::result::Result; constexpr static bool is_success(const ::sus::result::Result& t) { return t.is_ok(); } constexpr static Output into_output(::sus::result::Result t) { // SAFETY: The Result is verified to be holding Ok(T) by - // `::sus::ops::try_into_output()` before it calls here. + // `sus::ops::try_into_output()` before it calls here. return ::sus::move(t).unwrap_unchecked(::sus::marker::unsafe_fn); } constexpr static ::sus::result::Result from_output(Output t) { return ::sus::result::Result::with(::sus::move(t)); } + template + constexpr static ::sus::result::Result preserve_error( + ::sus::result::Result t) noexcept { + // SAFETY: The Result is verified to be holding Err(T) by + // `sus::ops::try_preserve_error()` before it calls here. + return ::sus::result::Result::with_err( + ::sus::move(t).unwrap_err_unchecked(::sus::marker::unsafe_fn)); + } // Implements sus::ops::TryDefault for `Result` if `T` satisfies // `Default`. constexpr static ::sus::result::Result from_default() noexcept @@ -1164,12 +1174,18 @@ struct sus::ops::TryImpl<::sus::result::Result> { template struct sus::ops::TryImpl<::sus::result::Result> { using Output = void; + template + using RemapOutput = ::sus::result::Result; constexpr static bool is_success( const ::sus::result::Result& t) noexcept { return t.is_ok(); } constexpr static void into_output(::sus::result::Result t) noexcept { } + constexpr static ::sus::result::Result preserve_error( + ::sus::result::Result t) noexcept { + ::sus::move(t); // Preserve the Err by returning it. + } // Implements sus::ops::TryDefault for `Result`. constexpr static ::sus::result::Result from_default() noexcept { return ::sus::result::Result::with(); @@ -1200,10 +1216,10 @@ struct sus::iter::FromIteratorImpl<::sus::result::Result> { ::sus::iter::FromIterator) { struct Unwrapper final : public ::sus::iter::IteratorBase { - Unwrapper(Iter&& iter, Option& err) : iter(iter), err(err) {} + constexpr Unwrapper(Iter&& iter, Option& err) : iter(iter), err(err) {} // sus::iter::Iterator trait. - Option next() noexcept { + constexpr Option next() noexcept { Option<::sus::result::Result> try_item = iter.next(); if (try_item.is_none()) return Option(); ::sus::result::Result result = @@ -1215,7 +1231,7 @@ struct sus::iter::FromIteratorImpl<::sus::result::Result> { ::sus::move(result).unwrap_err_unchecked(::sus::marker::unsafe_fn)); return Option(); } - ::sus::iter::SizeHint size_hint() const noexcept { + constexpr ::sus::iter::SizeHint size_hint() const noexcept { return ::sus::iter::SizeHint(0u, iter.size_hint().upper); } @@ -1225,11 +1241,11 @@ struct sus::iter::FromIteratorImpl<::sus::result::Result> { auto err = Option(); auto iter = Unwrapper(::sus::move(result_iter).into_iter(), err); - auto success_out = ::sus::result::Result::with( + auto out = ::sus::result::Result::with( ::sus::iter::from_iter(::sus::move(iter))); - return ::sus::move(err).map_or_else( - [&]() { return ::sus::move(success_out); }, - [](E e) { return ::sus::result::Result::with_err(e); }); + if (err.is_some()) + out = ::sus::result::Result::with_err(::sus::move(err).unwrap()); + return out; } };