Skip to content

Commit c39e102

Browse files
committed
Add Iterator::try_collect()
Closes chromium#295 try_collect can collect from an iterator of `T: Try` into a container C that supports FromIterator<T: Try> to create C<U>. The actual Try types do not need to support FromIterator then, and this replaces the need for Option or Result to implement FromIterator. We needed to add another operation on Try types for this, which is to convert from T: Try in an error state to U: Try in an error state, where they have the same (or a convertible) error type. This new concept is sus::ops::TryErrorConvertibleTo and it's accessed through sus::ops::try_preserve_error(). The TryErrorConvertibleTo concept is implemented for Option, Result and std::optional.
1 parent e5ad0b6 commit c39e102

12 files changed

+433
-33
lines changed

sus/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ target_sources(subspace PUBLIC
115115
"iter/iterator.h"
116116
"iter/iterator_concept.h"
117117
"iter/iterator_defn.h"
118+
"iter/iterator_impl.h"
118119
"iter/iterator_ref.h"
119120
"iter/once.h"
120121
"iter/product.h"
@@ -123,6 +124,7 @@ target_sources(subspace PUBLIC
123124
"iter/size_hint.h"
124125
"iter/size_hint_impl.h"
125126
"iter/successors.h"
127+
"iter/try_from_iterator.h"
126128
"iter/zip.h"
127129
"macros/__private/compiler_bugs.h"
128130
"macros/assume.h"

sus/iter/from_iterator.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ concept FromIterator =
5050
/// typically called through calling `collect()` on an iterator. However this
5151
/// function can be preferrable for some readers, especially in generic template
5252
/// code.
53-
template <class ToType, ::sus::iter::IntoIteratorAny IntoIter>
54-
requires(
55-
FromIterator<ToType, typename IntoIteratorOutputType<IntoIter>::Item>)
53+
template <class ToType, IntoIteratorAny IntoIter>
54+
requires(FromIterator<ToType,
55+
typename IntoIteratorOutputType<IntoIter>::Item> && //
56+
::sus::mem::IsMoveRef<IntoIter &&>)
5657
constexpr inline ToType from_iter(IntoIter&& into_iter) noexcept {
5758
return FromIteratorImpl<ToType>::from_iter(
5859
::sus::move(into_iter).into_iter());

sus/iter/iterator.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
// IWYU pragma: begin_exports
1818
#include "sus/iter/iterator_defn.h"
19+
#include "sus/iter/iterator_impl.h"
1920

2021
// The usize formatting is in unsigned_integer_impl.h which has an include cycle
2122
// with usize->Array->Array iterators->SizeHint->usize. So the SizeHint

sus/iter/iterator_defn.h

+61-8
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,57 @@ class IteratorBase {
927927
::sus::fn::FnMut<bool(const std::remove_reference_t<Item>&)> auto
928928
pred) && noexcept;
929929

930+
/// Fallibly transforms an iterator into a collection, short circuiting if a
931+
/// failure is encountered.
932+
///
933+
/// `try_collect()` is a variation of `collect()` that allows fallible
934+
/// conversions during collection. Its main use case is simplifying
935+
/// conversions from iterators yielding `Option<T>` into
936+
/// `Option<Collection<T>>`, or similarly for other Try types (e.g. `Result`
937+
/// or `std::optional`).
938+
///
939+
/// Importantly, `try_collect()` doesn’t require that the outer `Try` type
940+
/// also implements FromIterator; only the `Try` type's `Output` type must
941+
/// implement it. Concretely, this means that collecting into
942+
/// `TryThing<Vec<i32>, _>` can be valid because `Vec<i32>` implements
943+
/// FromIterator, even if `TryThing` doesn’t.
944+
///
945+
/// Also, if a failure is encountered during `try_collect()`, the iterator is
946+
/// still valid and may continue to be used, in which case it will continue
947+
/// iterating starting after the element that triggered the failure. See the
948+
/// last example below for an example of how this works.
949+
///
950+
/// # Examples
951+
/// Successfully collecting an iterator of `Option<i32>` into
952+
/// `Option<Vec<i32>>`:
953+
/// ```
954+
/// auto u = Vec<Option<i32>>::with(some(1), some(2), some(3));
955+
/// auto v = sus::move(u).into_iter().try_collect<Vec<i32>>();
956+
/// sus::check(v == some(Vec<i32>::with(1, 2, 3 )));
957+
/// ```
958+
/// Failing to collect in the same way:
959+
/// ```
960+
/// auto u = Vec<Option<i32>>::with(some(1), some(2), none(), some(3));
961+
/// auto v = sus::move(u).into_iter().try_collect<Vec<i32>>();
962+
/// sus::check(v == none());
963+
/// ```
964+
/// A similar example, but with [`Result`](sus::result::Result):
965+
/// ```
966+
/// enum Error { ERROR };
967+
/// auto u = Vec<Result<i32, Error>>::with(ok(1), ok(2), ok(3));
968+
/// auto v = sus::move(u).into_iter().try_collect<Vec<i32>>();
969+
/// sus::check(v == ok(Vec<i32>::with(1, 2, 3)));
970+
/// auto w = Vec<Result<i32, Error>>::with(ok(1), ok(2), err(ERROR), ok(3));
971+
/// auto x = sus::move(w).into_iter().try_collect<Vec<i32>>();
972+
/// sus::check(x == err(ERROR));
973+
/// ```
974+
template <class C>
975+
requires(::sus::ops::Try<ItemT> && //
976+
FromIterator<C, ::sus::ops::TryOutputType<ItemT>> &&
977+
// Void can not be collected from.
978+
!std::is_void_v<::sus::ops::TryOutputType<ItemT>>)
979+
constexpr auto try_collect() noexcept;
980+
930981
/// This function acts like `fold()` but the closure returns a type that
931982
/// satisfies `sus::ops::Try` and which converts to the accumulator type on
932983
/// success through the Try concept. If the closure ever returns failure, the
@@ -1059,7 +1110,7 @@ class IteratorBase {
10591110
/// sus::move(iter).collect<MyContainer<i32>>()
10601111
/// ```
10611112
template <FromIterator<ItemT> C>
1062-
constexpr FromIterator<ItemT> auto collect() && noexcept;
1113+
constexpr C collect() && noexcept;
10631114

10641115
/// Transforms an iterator into a Vec.
10651116
///
@@ -1433,7 +1484,8 @@ template <class Iter, class Item>
14331484
template <
14341485
::sus::fn::FnMut<::sus::fn::NonVoid(const std::remove_reference_t<Item>&)>
14351486
KeyFn,
1436-
int&..., class Key>
1487+
int&...,
1488+
class Key>
14371489
requires(::sus::ops::Ord<Key> && //
14381490
!std::is_reference_v<Key>)
14391491
constexpr Option<Item> IteratorBase<Iter, Item>::max_by_key(
@@ -1490,7 +1542,8 @@ template <class Iter, class Item>
14901542
template <
14911543
::sus::fn::FnMut<::sus::fn::NonVoid(const std::remove_reference_t<Item>&)>
14921544
KeyFn,
1493-
int&..., class Key>
1545+
int&...,
1546+
class Key>
14941547
requires(::sus::ops::Ord<Key> && //
14951548
!std::is_reference_v<Key>)
14961549
constexpr Option<Item> IteratorBase<Iter, Item>::min_by_key(
@@ -1699,7 +1752,8 @@ constexpr Option<usize> IteratorBase<Iter, Item>::rposition(
16991752

17001753
template <class Iter, class Item>
17011754
template <class State, ::sus::fn::FnMut<::sus::fn::NonVoid(State&, Item&&)> F,
1702-
int&..., class R, class InnerR>
1755+
int&..., class R,
1756+
class InnerR>
17031757
requires(::sus::option::__private::IsOptionType<R>::value && //
17041758
!std::is_reference_v<State>)
17051759
constexpr Iterator<InnerR> auto IteratorBase<Iter, Item>::scan(
@@ -1859,14 +1913,13 @@ constexpr std::weak_ordering IteratorBase<Iter, Item>::weak_cmp_by(
18591913

18601914
template <class Iter, class Item>
18611915
template <FromIterator<Item> C>
1862-
constexpr FromIterator<Item> auto
1863-
IteratorBase<Iter, Item>::collect() && noexcept {
1916+
constexpr C IteratorBase<Iter, Item>::collect() && noexcept {
18641917
return from_iter<C>(static_cast<Iter&&>(*this));
18651918
}
18661919

18671920
template <class Iter, class Item>
1868-
::sus::containers::Vec<Item> constexpr IteratorBase<
1869-
Iter, Item>::collect_vec() && noexcept {
1921+
constexpr ::sus::containers::Vec<Item>
1922+
IteratorBase<Iter, Item>::collect_vec() && noexcept {
18701923
return from_iter<::sus::containers::Vec<Item>>(static_cast<Iter&&>(*this));
18711924
}
18721925

sus/iter/iterator_impl.h

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// IWYU pragma: private, include "sus/iter/iterator.h"
16+
// IWYU pragma: friend "sus/.*"
17+
#pragma once
18+
19+
#include <type_traits>
20+
21+
#include "sus/iter/try_from_iterator.h"
22+
23+
namespace sus::iter {
24+
25+
// Implementation of IteratorBase::try_collect has to live here to avoid
26+
// cyclical includes, as the impl of try_from_iter() needs to see IteratorBase
27+
// in order to make something that can be consumed by FromIterator.
28+
template <class Iter, class Item>
29+
template <class C>
30+
requires(::sus::ops::Try<Item> && //
31+
FromIterator<C, ::sus::ops::TryOutputType<Item>> &&
32+
// Void can not be collected from.
33+
!std::is_void_v<::sus::ops::TryOutputType<Item>>)
34+
constexpr auto IteratorBase<Iter, Item>::try_collect() noexcept {
35+
return try_from_iter<C>(static_cast<Iter&&>(*this));
36+
}
37+
38+
} // namespace sus::iter

sus/iter/iterator_unittest.cc

+112
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@
3333
#include "sus/num/overflow_integer.h"
3434
#include "sus/ops/eq.h"
3535
#include "sus/prelude.h"
36+
#include "sus/test/no_copy_move.h"
3637

3738
using sus::containers::Array;
3839
using sus::iter::IteratorBase;
3940
using sus::option::Option;
41+
using sus::result::Result;
42+
using sus::test::NoCopyMove;
4043

4144
namespace sus::test::iter {
4245

@@ -45,6 +48,10 @@ struct CollectSum {
4548
T sum;
4649
};
4750

51+
struct CollectRefs {
52+
sus::Vec<const NoCopyMove*> vec;
53+
};
54+
4855
} // namespace sus::test::iter
4956

5057
template <class T>
@@ -57,6 +64,16 @@ struct sus::iter::FromIteratorImpl<sus::test::iter::CollectSum<T>> {
5764
}
5865
};
5966

67+
template <>
68+
struct sus::iter::FromIteratorImpl<sus::test::iter::CollectRefs> {
69+
static sus::test::iter::CollectRefs from_iter(
70+
sus::iter::IntoIterator<const NoCopyMove&> auto iter) noexcept {
71+
auto v = sus::Vec<const NoCopyMove*>();
72+
for (const NoCopyMove& t : sus::move(iter).into_iter()) v.push(&t);
73+
return sus::test::iter::CollectRefs(sus::move(v));
74+
}
75+
};
76+
6077
namespace {
6178
using namespace sus::test::iter;
6279

@@ -460,6 +477,10 @@ TEST(Iterator, Collect) {
460477
ArrayIterator<i32, 5>::with_array(nums).collect<CollectSum<i32>>();
461478
EXPECT_EQ(collected.sum, 1 + 2 + 3 + 4 + 5);
462479

480+
auto from = sus::iter::from_iter<CollectSum<i32>>(
481+
ArrayIterator<i32, 5>::with_array(nums));
482+
EXPECT_EQ(from.sum, 1 + 2 + 3 + 4 + 5);
483+
463484
static_assert(sus::Array<i32, 5>::with(1, 2, 3, 4, 5)
464485
.into_iter()
465486
.collect<CollectSum<i32>>()
@@ -481,6 +502,97 @@ TEST(Iterator, CollectVec) {
481502
sus::Vec<i32>::with(1, 2, 3, 4, 5));
482503
}
483504

505+
TEST(Iterator, TryCollect) {
506+
// Option.
507+
{
508+
auto collected = sus::Array<Option<i32>, 3>::with(
509+
::sus::some(1), ::sus::some(2), ::sus::some(3))
510+
.into_iter()
511+
.try_collect<Vec<i32>>();
512+
static_assert(std::same_as<decltype(collected), Option<Vec<i32>>>);
513+
EXPECT_EQ(collected.is_some(), true);
514+
EXPECT_EQ(collected.as_value(), sus::Vec<i32>::with(1, 2, 3));
515+
516+
auto it = sus::Array<Option<i32>, 3>::with(::sus::some(1), ::sus::none(),
517+
::sus::some(3))
518+
.into_iter();
519+
auto up_to_none = it.try_collect<Vec<i32>>();
520+
EXPECT_EQ(up_to_none, sus::none());
521+
auto after_none = it.try_collect<Vec<i32>>();
522+
EXPECT_EQ(after_none.as_value(), sus::Vec<i32>::with(3));
523+
524+
NoCopyMove n[3];
525+
auto refs = sus::Array<Option<const NoCopyMove&>, 3>::with(
526+
::sus::some(n[0]), ::sus::some(n[1]), ::sus::some(n[2]))
527+
.into_iter()
528+
.try_collect<CollectRefs>();
529+
EXPECT_EQ(refs.is_some(), true);
530+
EXPECT_EQ(refs.as_value().vec[0u], &n[0]);
531+
EXPECT_EQ(refs.as_value().vec[1u], &n[1]);
532+
EXPECT_EQ(refs.as_value().vec[2u], &n[2]);
533+
}
534+
// Result.
535+
enum Error { ERROR };
536+
{
537+
auto collected = sus::Array<Result<i32, Error>, 3>::with(
538+
::sus::ok(1), ::sus::ok(2), ::sus::ok(3))
539+
.into_iter()
540+
.try_collect<Vec<i32>>();
541+
static_assert(std::same_as<decltype(collected), Result<Vec<i32>, Error>>);
542+
EXPECT_EQ(collected.as_ok(), sus::Vec<i32>::with(1, 2, 3));
543+
544+
auto it = sus::Array<Result<i32, Error>, 3>::with(
545+
::sus::ok(1), ::sus::err(ERROR), ::sus::ok(3))
546+
.into_iter();
547+
auto up_to_err = it.try_collect<Vec<i32>>();
548+
EXPECT_EQ(up_to_err, sus::err(ERROR));
549+
auto after_err = it.try_collect<Vec<i32>>();
550+
EXPECT_EQ(after_err.as_ok(), sus::Vec<i32>::with(3));
551+
}
552+
553+
auto from =
554+
sus::iter::try_from_iter<Vec<i32>>(sus::Array<Option<i32>, 3>::with(
555+
::sus::some(1), ::sus::some(2), ::sus::some(3)));
556+
EXPECT_EQ(from.as_value(), sus::Vec<i32>::with(1, 2, 3));
557+
558+
static_assert(
559+
sus::Array<Option<i32>, 3>::with(sus::some(1), sus::some(2), sus::some(3))
560+
.into_iter()
561+
.try_collect<Vec<i32>>()
562+
.unwrap()
563+
.into_iter()
564+
.sum() == 1 + 2 + 3);
565+
}
566+
567+
TEST(Iterator, TryCollect_Example) {
568+
using sus::none;
569+
using sus::ok;
570+
using sus::err;
571+
using sus::Option;
572+
using sus::result::Result;
573+
using sus::some;
574+
using sus::Vec;
575+
{
576+
auto u = Vec<Option<i32>>::with(some(1), some(2), some(3));
577+
auto v = sus::move(u).into_iter().try_collect<Vec<i32>>();
578+
sus::check(v == some(Vec<i32>::with(1, 2, 3)));
579+
}
580+
{
581+
auto u = Vec<Option<i32>>::with(some(1), some(2), none(), some(3));
582+
auto v = sus::move(u).into_iter().try_collect<Vec<i32>>();
583+
sus::check(v == none());
584+
}
585+
{
586+
enum Error { ERROR };
587+
auto u = Vec<Result<i32, Error>>::with(ok(1), ok(2), ok(3));
588+
auto v = sus::move(u).into_iter().try_collect<Vec<i32>>();
589+
sus::check(v == ok(Vec<i32>::with(1, 2, 3)));
590+
auto w = Vec<Result<i32, Error>>::with(ok(1), ok(2), err(ERROR), ok(3));
591+
auto x = sus::move(w).into_iter().try_collect<Vec<i32>>();
592+
sus::check(x == err(ERROR));
593+
}
594+
}
595+
484596
TEST(Iterator, Rev) {
485597
i32 nums[5] = {1, 2, 3, 4, 5};
486598

0 commit comments

Comments
 (0)