Nix 2.93.3
Lix: A modern, delicious implementation of the Nix package manager; unstable internal interfaces
Loading...
Searching...
No Matches
generator.hh
Go to the documentation of this file.
1#pragma once
3
5
6#include <coroutine>
7#include <exception>
8#include <memory>
9#include <optional>
10#include <utility>
11#include <variant>
12
13namespace nix {
14
15template<typename T, typename Transform>
16struct Generator;
17
18namespace _generator {
19
20template<typename T>
21struct promise_state;
22template<typename T>
23struct GeneratorBase;
24
25struct finished {};
26
27template<typename T>
28struct link
29{
30 std::coroutine_handle<> handle{};
31 promise_state<T> * state{};
32};
33
34struct failure
35{
36 std::exception_ptr e;
37};
38
39template<typename T>
41{
42 // result of the most recent coroutine resumption: a nested
43 // coroutine to drain, a value, an error, or our completion
44 std::variant<link<T>, T, failure, finished> value{};
45 // coroutine to resume when this one has finished. set when
46 // one generator yields another, such that the entire chain
47 // of parents always linearly points to the root generator.
48 link<T> parent{};
49};
50
51template<typename T, typename Transform>
53{
54 using transform_t = std::conditional_t<std::is_void_v<Transform>, std::identity, Transform>;
55
56 transform_t convert;
57 std::optional<GeneratorBase<T>> inner;
58
59 // called by the compiler to convert the internal promise object
60 // to the user-declared (function return) type of the coroutine.
61 Generator<T, Transform> get_return_object()
62 {
63 auto h = std::coroutine_handle<promise>::from_promise(*this);
64 return Generator<T, Transform>(GeneratorBase<T>(h, h.promise()));
65 }
66 std::suspend_always initial_suspend()
67 {
68 return {};
69 }
70 std::suspend_always final_suspend() noexcept
71 {
72 return {};
73 }
74 void unhandled_exception()
75 {
76 this->value = failure{std::current_exception()};
77 }
78
79 // `co_yield` handler for "simple" values, i.e. those that
80 // are transformed directly to a T by the given transform.
81 template<typename From>
82 requires requires(transform_t t, From && f) {
83 {
84 t(std::forward<From>(f))
85 } -> std::convertible_to<T>;
86 }
87 std::suspend_always yield_value(From && from)
88 {
89 this->value.template emplace<1>(convert(std::forward<From>(from)));
90 return {};
91 }
92
93 // `co_yield` handler for "complex" values, i.e. those that
94 // are transformed into another generator. we'll drain that
95 // new generator completely before resuming the current one
96 template<typename From>
97 requires requires(transform_t t, From && f) {
98 static_cast<Generator<T, void>>(t(std::forward<From>(f)));
99 }
100 std::suspend_always yield_value(From && from)
101 {
102 inner = static_cast<Generator<T, void>>(convert(std::forward<From>(from))).impl;
103 this->value = inner->active;
104 return {};
105 }
106
107 // handler for `co_return`, including the implicit `co_return`
108 // at the end of a coroutine that does not have one explicitly
109 void return_void()
110 {
111 this->value = finished{};
112 }
113};
114
115template<typename T>
116struct GeneratorBase
117{
118 template<typename, typename>
119 friend struct Generator;
120 template<typename, typename>
121 friend struct promise;
122
123 // NOTE coroutine handles are LiteralType, own a memory resource (that may
124 // itself own unique resources), and are "typically TriviallyCopyable". we
125 // need to take special care to wrap this into a less footgunny interface.
126 GeneratorBase(GeneratorBase && other)
127 {
128 swap(other);
129 }
130
131 GeneratorBase & operator=(GeneratorBase && other)
132 {
133 GeneratorBase(std::move(other)).swap(*this);
134 return *this;
135 }
136
137 ~GeneratorBase()
138 {
139 if (h) {
140 h.destroy();
141 }
142 }
143
144 std::optional<T> next()
145 {
146 // resume the currently active coroutine once. it can return either a
147 // value, an exception, another generator to drain, or it can finish.
148 // since c++ coroutines cannot directly return anything from resume()
149 // we must communicate all results via `active->state.value` instead.
150 while (active.handle) {
151 active.handle.resume();
152 auto & p = *active.state;
153 // process the result. only one case sets this to a non-`nullopt`
154 // value, all others leave it at `nullopt` to request more loops.
155 auto result = std::visit(
157 // when the current coroutine handle is done we'll try to
158 // resume its parent (if the current handle was retrieved
159 // from a `co_yield`ed generator) or finish the generator
160 // entirely because the root active.parent has no handle.
161 [&](finished) -> std::optional<T> {
162 active = p.parent;
163 return {};
164 },
165 // when the coroutine yields a generator we push the full
166 // inner stack onto our own stack and resume the top item
167 [&](link<T> & inner) -> std::optional<T> {
168 auto base = inner.state;
169 while (base->parent.handle) {
170 base = base->parent.state;
171 }
172 base->parent = active;
173 active = inner;
174 return {};
175 },
176 // values are simply returned to the caller, as received.
177 [&](T & value) -> std::optional<T> { return std::move(value); },
178 // exceptions must be rethrown. resuming again after this
179 // is not allowed because the top-most coroutine would be
180 // finished and we'd thus step back to its parent, but by
181 // doing so we might invalidate invariants of the parent.
182 // allowing the parent to catch exceptions of a child for
183 // `co_yield` exceptions specifically would introduce far
184 // too many problems to be worth the doing (since parents
185 // can neither know nor revert any yields of their child)
186 [&](failure & f) -> std::optional<T> {
187 active = {};
188 std::rethrow_exception(f.e);
189 },
190 },
191 p.value
192 );
193 if (result) {
194 return result;
195 }
196 }
197
198 return std::nullopt;
199 }
200
201protected:
202 std::coroutine_handle<> h{};
203 link<T> active{};
204
205 GeneratorBase(std::coroutine_handle<> h, promise_state<T> & state)
206 : h(h)
207 , active(h, &state)
208 {
209 }
210
211 void swap(GeneratorBase & other)
212 {
213 std::swap(h, other.h);
214 std::swap(active, other.active);
215 }
216};
217
218} // _generator
219
234template<typename T, typename Transform = void>
235struct Generator
236{
237 template<typename, typename>
238 friend struct _generator::promise;
239 // erasing the Transform type requires all generator types with a non-erased
240 // Transform to access the private constructor of the erased type, but sadly
241 // we cannot resonably restrict this to "T, non-void" without much more code
242 // or compiler warnings on some versions of clang, e.g. the one darwin uses.
243 template<typename, typename>
244 friend struct Generator;
245
246 using promise_type = _generator::promise<T, Transform>;
247
248 Generator(const Generator &) = delete;
249 Generator & operator=(const Generator &) = delete;
250 Generator(Generator &&) = default;
251 Generator & operator=(Generator &&) = default;
252
265 std::optional<T> next()
266 {
267 return impl.next();
268 }
269
273 Generator<T, void> decay() &&
274 {
275 return Generator<T, void>(std::move(impl));
276 }
277
279 operator Generator<T, void>() &&
280 {
281 return std::move(*this).decay();
282 }
283
284 class iterator
285 {
286 // operator== must be const, but we need to call parent->next() to
287 // be able to check whether the sequence has ended. boldface sigh.
288 mutable Generator * parent = nullptr;
289 mutable std::shared_ptr<T> item = nullptr;
290
291 void step() const
292 {
293 auto next = parent->next();
294 if (!next) {
295 parent = nullptr;
296 item = nullptr;
297 } else if (!item) {
298 item = std::make_shared<T>(std::move(*next));
299 } else {
300 *item = std::move(*next);
301 }
302 }
303
304 void initializeIfNecessary() const
305 {
306 if (parent && !item) {
307 step();
308 }
309 }
310
311 public:
312 using iterator_category = std::input_iterator_tag;
313 using difference_type = void;
314 using value_type = T;
315 using reference = T &;
316 using pointer = T *;
317
318 iterator() = default;
319 explicit iterator(Generator & parent) : parent(&parent) {}
320
321 T * operator->() { return &**this; }
322 T & operator*()
323 {
324 initializeIfNecessary();
325 return *item;
326 }
327
328 iterator & operator++()
329 {
330 initializeIfNecessary();
331 if (parent) {
332 step();
333 }
334 return *this;
335 }
336
337 void operator++(int) { ++*this; }
338
339 bool operator==(const iterator & b) const
340 {
341 initializeIfNecessary();
342 return parent == nullptr && b.parent == nullptr;
343 }
344 };
345
346 iterator begin() { return iterator{*this}; }
347 iterator end() { return iterator{}; }
348
349private:
350 _generator::GeneratorBase<T> impl;
351
352 explicit Generator(_generator::GeneratorBase<T> b) : impl(std::move(b)) {}
353};
354
355}
Definition generator.hh:285
Definition generator.hh:236
std::optional< T > next()
Definition generator.hh:265
Generator< T, void > decay() &&
Definition generator.hh:273
Definition generator.hh:117
Definition generator.hh:35
Definition generator.hh:25
Definition generator.hh:41
Definition generator.hh:53
Definition types.hh:164