snix_eval/vm/
generators.rs

1//! This module implements generator logic for the VM. Generators are functions
2//! used during evaluation which can suspend their execution during their
3//! control flow, and request that the VM do something.
4//!
5//! This is used to keep the VM's stack size constant even when evaluating
6//! deeply nested recursive data structures.
7//!
8//! We implement generators using the [`genawaiter`] crate.
9
10use core::pin::Pin;
11use genawaiter::rc::Co;
12pub use genawaiter::rc::Gen;
13use std::fmt::Display;
14use std::future::Future;
15
16use crate::value::PointerEquality;
17use crate::warnings::{EvalWarning, WarningKind};
18use crate::FileType;
19use crate::NixString;
20
21use super::*;
22
23// -- Implementation of generic generator logic.
24
25/// States that a generator can be in while being driven by the VM.
26pub(crate) enum GeneratorState {
27    /// Normal execution of the generator.
28    Running,
29
30    /// Generator is awaiting the result of a forced value.
31    AwaitingValue,
32}
33
34/// Messages that can be sent from generators *to* the VM. In most
35/// cases, the VM will suspend the generator when receiving a message
36/// and enter some other frame to process the request.
37///
38/// Responses are returned to generators via the [`VMResponse`] type.
39pub enum VMRequest {
40    /// Request that the VM forces this value. This message is first sent to the
41    /// VM with the unforced value, then returned to the generator with the
42    /// forced result.
43    ForceValue(Value),
44
45    /// Request that the VM deep-forces the value.
46    DeepForceValue(Value),
47
48    /// Request the value at the given index from the VM's with-stack, in forced
49    /// state.
50    ///
51    /// The value is returned in the `ForceValue` message.
52    WithValue(usize),
53
54    /// Request the value at the given index from the *captured* with-stack, in
55    /// forced state.
56    CapturedWithValue(usize),
57
58    /// Request that the two values be compared for Nix equality. The result is
59    /// returned in the `ForceValue` message.
60    NixEquality(Box<(Value, Value)>, PointerEquality),
61
62    /// Push the given value to the VM's stack. This is used to prepare the
63    /// stack for requesting a function call from the VM.
64    ///
65    /// The VM does not respond to this request, so the next message received is
66    /// `Empty`.
67    StackPush(Value),
68
69    /// Pop a value from the stack and return it to the generator.
70    StackPop,
71
72    /// Request that the VM coerces this value to a string.
73    StringCoerce(Value, CoercionKind),
74
75    /// Request that the VM calls the given value, with arguments already
76    /// prepared on the stack. Value must already be forced.
77    Call(Value),
78
79    /// Request a call frame entering the given lambda immediately. This can be
80    /// used to force thunks.
81    EnterLambda {
82        lambda: Rc<Lambda>,
83        upvalues: Rc<Upvalues>,
84        span: Span,
85    },
86
87    /// Emit a runtime warning (already containing a span) through the VM.
88    EmitWarning(EvalWarning),
89
90    /// Emit a runtime warning through the VM. The span of the current generator
91    /// is used for the final warning.
92    EmitWarningKind(WarningKind),
93
94    /// Request a lookup in the VM's import cache, which tracks the
95    /// thunks yielded by previously imported files.
96    ImportCacheLookup(PathBuf),
97
98    /// Provide the VM with an imported value for a given path, which
99    /// it can populate its input cache with.
100    ImportCachePut(PathBuf, Value),
101
102    /// Request that the VM imports the given path through its I/O interface.
103    PathImport(PathBuf),
104
105    /// Request that the VM opens the specified file and provides a reader.
106    OpenFile(PathBuf),
107
108    /// Request that the VM checks whether the given path exists.
109    PathExists(PathBuf),
110
111    /// Request that the VM reads the given path.
112    ReadDir(PathBuf),
113
114    /// Request a reasonable span from the VM.
115    Span,
116
117    /// Request evaluation of `builtins.tryEval` from the VM.
118    TryForce(Value),
119
120    /// Request the VM for the file type of the given path.
121    ReadFileType(PathBuf),
122}
123
124/// Human-readable representation of a generator message, used by observers.
125impl Display for VMRequest {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            VMRequest::ForceValue(v) => write!(f, "force_value({})", v.type_of()),
129            VMRequest::DeepForceValue(v) => {
130                write!(f, "deep_force_value({})", v.type_of())
131            }
132            VMRequest::WithValue(_) => write!(f, "with_value"),
133            VMRequest::CapturedWithValue(_) => write!(f, "captured_with_value"),
134            VMRequest::NixEquality(values, ptr_eq) => {
135                write!(
136                    f,
137                    "nix_eq({}, {}, PointerEquality::{:?})",
138                    values.0.type_of(),
139                    values.1.type_of(),
140                    ptr_eq
141                )
142            }
143            VMRequest::StackPush(v) => write!(f, "stack_push({})", v.type_of()),
144            VMRequest::StackPop => write!(f, "stack_pop"),
145            VMRequest::StringCoerce(
146                v,
147                CoercionKind {
148                    strong,
149                    import_paths,
150                },
151            ) => write!(
152                f,
153                "{}_{}importing_string_coerce({})",
154                if *strong { "strong" } else { "weak" },
155                if *import_paths { "" } else { "non_" },
156                v.type_of()
157            ),
158            VMRequest::Call(v) => write!(f, "call({})", v),
159            VMRequest::EnterLambda { lambda, .. } => {
160                write!(f, "enter_lambda({:p})", *lambda)
161            }
162            VMRequest::EmitWarning(_) => write!(f, "emit_warning"),
163            VMRequest::EmitWarningKind(_) => write!(f, "emit_warning_kind"),
164            VMRequest::ImportCacheLookup(p) => {
165                write!(f, "import_cache_lookup({})", p.to_string_lossy())
166            }
167            VMRequest::ImportCachePut(p, _) => {
168                write!(f, "import_cache_put({})", p.to_string_lossy())
169            }
170            VMRequest::PathImport(p) => write!(f, "path_import({})", p.to_string_lossy()),
171            VMRequest::OpenFile(p) => {
172                write!(f, "open_file({})", p.to_string_lossy())
173            }
174            VMRequest::PathExists(p) => write!(f, "path_exists({})", p.to_string_lossy()),
175            VMRequest::ReadDir(p) => write!(f, "read_dir({})", p.to_string_lossy()),
176            VMRequest::Span => write!(f, "span"),
177            VMRequest::TryForce(v) => write!(f, "try_force({})", v.type_of()),
178            VMRequest::ReadFileType(p) => write!(f, "read_file_type({})", p.to_string_lossy()),
179        }
180    }
181}
182
183/// Responses returned to generators *from* the VM.
184pub enum VMResponse {
185    /// Empty message. Passed to the generator as the first message,
186    /// or when return values were optional.
187    Empty,
188
189    /// Value produced by the VM and returned to the generator.
190    Value(Value),
191
192    /// Path produced by the VM in response to some IO operation.
193    Path(PathBuf),
194
195    /// VM response with the contents of a directory.
196    Directory(Vec<(bytes::Bytes, FileType)>),
197
198    /// VM response with a span to use at the current point.
199    Span(Span),
200
201    /// Reader produced by the VM in response to some IO operation.
202    Reader(Box<dyn std::io::Read>),
203
204    FileType(FileType),
205}
206
207impl Display for VMResponse {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        match self {
210            VMResponse::Empty => write!(f, "empty"),
211            VMResponse::Value(v) => write!(f, "value({})", v),
212            VMResponse::Path(p) => write!(f, "path({})", p.to_string_lossy()),
213            VMResponse::Directory(d) => write!(f, "dir(len = {})", d.len()),
214            VMResponse::Span(_) => write!(f, "span"),
215            VMResponse::Reader(_) => write!(f, "reader"),
216            VMResponse::FileType(t) => write!(f, "file_type({})", t),
217        }
218    }
219}
220
221pub(crate) type Generator =
222    Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>;
223
224/// Helper function to provide type annotations which are otherwise difficult to
225/// infer.
226pub fn pin_generator(
227    f: impl Future<Output = Result<Value, ErrorKind>> + 'static,
228) -> Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>> {
229    Box::pin(f)
230}
231
232impl<IO> VM<'_, IO>
233where
234    IO: AsRef<dyn EvalIO> + 'static,
235{
236    /// Helper function to re-enqueue the current generator while it
237    /// is awaiting a value.
238    fn reenqueue_generator(&mut self, name: &'static str, span: Span, generator: Generator) {
239        self.frames.push(Frame::Generator {
240            name,
241            generator,
242            span,
243            state: GeneratorState::AwaitingValue,
244        });
245    }
246
247    /// Helper function to enqueue a new generator.
248    pub(super) fn enqueue_generator<F, G>(&mut self, name: &'static str, span: Span, gen: G)
249    where
250        F: Future<Output = Result<Value, ErrorKind>> + 'static,
251        G: FnOnce(GenCo) -> F,
252    {
253        self.frames.push(Frame::Generator {
254            name,
255            span,
256            state: GeneratorState::Running,
257            generator: Gen::new(|co| pin_generator(gen(co))),
258        });
259    }
260
261    /// Run a generator frame until it yields to the outer control loop, or runs
262    /// to completion.
263    ///
264    /// The return value indicates whether the generator has completed (true),
265    /// or was suspended (false).
266    pub(crate) fn run_generator(
267        &mut self,
268        name: &'static str,
269        span: Span,
270        frame_id: usize,
271        state: GeneratorState,
272        mut generator: Generator,
273        initial_message: Option<VMResponse>,
274    ) -> EvalResult<bool> {
275        // Determine what to send to the generator based on its state.
276        let mut message = match (initial_message, state) {
277            (Some(msg), _) => msg,
278            (_, GeneratorState::Running) => VMResponse::Empty,
279
280            // If control returned here, and the generator is
281            // awaiting a value, send it the top of the stack.
282            (_, GeneratorState::AwaitingValue) => VMResponse::Value(self.stack_pop()),
283        };
284
285        loop {
286            match generator.resume_with(message) {
287                // If the generator yields, it contains an instruction
288                // for what the VM should do.
289                genawaiter::GeneratorState::Yielded(request) => {
290                    self.observer.observe_generator_request(name, &request);
291
292                    match request {
293                        VMRequest::StackPush(value) => {
294                            self.stack.push(value);
295                            message = VMResponse::Empty;
296                        }
297
298                        VMRequest::StackPop => {
299                            message = VMResponse::Value(self.stack_pop());
300                        }
301
302                        // Generator has requested a force, which means that
303                        // this function prepares the frame stack and yields
304                        // back to the outer VM loop.
305                        VMRequest::ForceValue(value) => {
306                            self.reenqueue_generator(name, span, generator);
307                            self.enqueue_generator("force", span, |co| {
308                                value.force_owned_genco(co, span)
309                            });
310                            return Ok(false);
311                        }
312
313                        // Generator has requested a deep-force.
314                        VMRequest::DeepForceValue(value) => {
315                            self.reenqueue_generator(name, span, generator);
316                            self.enqueue_generator("deep_force", span, |co| {
317                                value.deep_force(co, span)
318                            });
319                            return Ok(false);
320                        }
321
322                        // Generator has requested a value from the with-stack.
323                        // Logic is similar to `ForceValue`, except with the
324                        // value being taken from that stack.
325                        VMRequest::WithValue(idx) => {
326                            self.reenqueue_generator(name, span, generator);
327
328                            let value = self.stack[self.with_stack[idx]].clone();
329                            self.enqueue_generator("force", span, |co| {
330                                value.force_owned_genco(co, span)
331                            });
332
333                            return Ok(false);
334                        }
335
336                        // Generator has requested a value from the *captured*
337                        // with-stack. Logic is same as above, except for the
338                        // value being from that stack.
339                        VMRequest::CapturedWithValue(idx) => {
340                            self.reenqueue_generator(name, span, generator);
341
342                            let call_frame = self.last_call_frame()
343                                .expect("Snix bug: generator requested captured with-value, but there is no call frame");
344
345                            let value = call_frame.upvalues.with_stack().unwrap()[idx].clone();
346                            self.enqueue_generator("force", span, |co| {
347                                value.force_owned_genco(co, span)
348                            });
349
350                            return Ok(false);
351                        }
352
353                        VMRequest::NixEquality(values, ptr_eq) => {
354                            let values = *values;
355                            self.reenqueue_generator(name, span, generator);
356                            self.enqueue_generator("nix_eq", span, |co| {
357                                values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
358                            });
359                            return Ok(false);
360                        }
361
362                        VMRequest::StringCoerce(val, kind) => {
363                            self.reenqueue_generator(name, span, generator);
364                            self.enqueue_generator("coerce_to_string", span, |co| {
365                                val.coerce_to_string(co, kind, span)
366                            });
367                            return Ok(false);
368                        }
369
370                        VMRequest::Call(callable) => {
371                            self.reenqueue_generator(name, span, generator);
372                            self.call_value(span, None, callable)?;
373                            return Ok(false);
374                        }
375
376                        VMRequest::EnterLambda {
377                            lambda,
378                            upvalues,
379                            span,
380                        } => {
381                            self.reenqueue_generator(name, span, generator);
382
383                            self.frames.push(Frame::CallFrame {
384                                span,
385                                call_frame: CallFrame {
386                                    lambda,
387                                    upvalues,
388                                    ip: CodeIdx(0),
389                                    stack_offset: self.stack.len(),
390                                },
391                            });
392
393                            return Ok(false);
394                        }
395
396                        VMRequest::EmitWarning(warning) => {
397                            self.push_warning(warning);
398                            message = VMResponse::Empty;
399                        }
400
401                        VMRequest::EmitWarningKind(kind) => {
402                            self.emit_warning(kind);
403                            message = VMResponse::Empty;
404                        }
405
406                        VMRequest::ImportCacheLookup(path) => {
407                            if let Some(cached) = self.import_cache.get(path) {
408                                message = VMResponse::Value(cached.clone());
409                            } else {
410                                message = VMResponse::Empty;
411                            }
412                        }
413
414                        VMRequest::ImportCachePut(path, value) => {
415                            self.import_cache.insert(path, value);
416                            message = VMResponse::Empty;
417                        }
418
419                        VMRequest::PathImport(path) => {
420                            let imported = self
421                                .io_handle
422                                .as_ref()
423                                .import_path(&path)
424                                .map_err(|e| ErrorKind::IO {
425                                    path: Some(path),
426                                    error: e.into(),
427                                })
428                                .with_span(span, self)?;
429
430                            message = VMResponse::Path(imported);
431                        }
432
433                        VMRequest::OpenFile(path) => {
434                            let reader = self
435                                .io_handle
436                                .as_ref()
437                                .open(&path)
438                                .map_err(|e| ErrorKind::IO {
439                                    path: Some(path),
440                                    error: e.into(),
441                                })
442                                .with_span(span, self)?;
443
444                            message = VMResponse::Reader(reader)
445                        }
446
447                        VMRequest::PathExists(path) => {
448                            let exists = self
449                                .io_handle
450                                .as_ref()
451                                .path_exists(&path)
452                                .map_err(|e| ErrorKind::IO {
453                                    path: Some(path),
454                                    error: e.into(),
455                                })
456                                .map(Value::Bool)
457                                .with_span(span, self)?;
458
459                            message = VMResponse::Value(exists);
460                        }
461
462                        VMRequest::ReadDir(path) => {
463                            let dir = self
464                                .io_handle
465                                .as_ref()
466                                .read_dir(&path)
467                                .map_err(|e| ErrorKind::IO {
468                                    path: Some(path),
469                                    error: e.into(),
470                                })
471                                .with_span(span, self)?;
472                            message = VMResponse::Directory(dir);
473                        }
474
475                        VMRequest::Span => {
476                            message = VMResponse::Span(self.reasonable_span);
477                        }
478
479                        VMRequest::TryForce(value) => {
480                            self.try_eval_frames.push(frame_id);
481                            self.reenqueue_generator(name, span, generator);
482
483                            debug_assert!(
484                                self.frames.len() == frame_id + 1,
485                                "generator should be reenqueued with the same frame ID"
486                            );
487
488                            self.enqueue_generator("force", span, |co| {
489                                value.force_owned_genco(co, span)
490                            });
491                            return Ok(false);
492                        }
493
494                        VMRequest::ReadFileType(path) => {
495                            let file_type = self
496                                .io_handle
497                                .as_ref()
498                                .file_type(&path)
499                                .map_err(|e| ErrorKind::IO {
500                                    path: Some(path),
501                                    error: e.into(),
502                                })
503                                .with_span(span, self)?;
504
505                            message = VMResponse::FileType(file_type);
506                        }
507                    }
508                }
509
510                // Generator has completed, and its result value should
511                // be left on the stack.
512                genawaiter::GeneratorState::Complete(result) => {
513                    let value = result.with_span(span, self)?;
514                    self.stack.push(value);
515                    return Ok(true);
516                }
517            }
518        }
519    }
520}
521
522pub type GenCo = Co<VMRequest, VMResponse>;
523
524// -- Implementation of concrete generator use-cases.
525
526/// Request that the VM place the given value on its stack.
527pub async fn request_stack_push(co: &GenCo, val: Value) {
528    match co.yield_(VMRequest::StackPush(val)).await {
529        VMResponse::Empty => {}
530        msg => panic!(
531            "Snix bug: VM responded with incorrect generator message: {}",
532            msg
533        ),
534    }
535}
536
537/// Request that the VM pop a value from the stack and return it to the
538/// generator.
539pub async fn request_stack_pop(co: &GenCo) -> Value {
540    match co.yield_(VMRequest::StackPop).await {
541        VMResponse::Value(value) => value,
542        msg => panic!(
543            "Snix bug: VM responded with incorrect generator message: {}",
544            msg
545        ),
546    }
547}
548
549/// Force any value and return the evaluated result from the VM.
550pub async fn request_force(co: &GenCo, val: Value) -> Value {
551    if let Value::Thunk(_) = val {
552        match co.yield_(VMRequest::ForceValue(val)).await {
553            VMResponse::Value(value) => value,
554            msg => panic!(
555                "Snix bug: VM responded with incorrect generator message: {}",
556                msg
557            ),
558        }
559    } else {
560        val
561    }
562}
563
564/// Force a value
565pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
566    if let Value::Thunk(_) = val {
567        match co.yield_(VMRequest::TryForce(val)).await {
568            VMResponse::Value(value) => value,
569            msg => panic!(
570                "Snix bug: VM responded with incorrect generator message: {}",
571                msg
572            ),
573        }
574    } else {
575        val
576    }
577}
578
579/// Call the given value as a callable. The argument(s) must already be prepared
580/// on the stack.
581pub async fn request_call(co: &GenCo, val: Value) -> Value {
582    let val = request_force(co, val).await;
583    match co.yield_(VMRequest::Call(val)).await {
584        VMResponse::Value(value) => value,
585        msg => panic!(
586            "Snix bug: VM responded with incorrect generator message: {}",
587            msg
588        ),
589    }
590}
591
592/// Helper function to call the given value with the provided list of arguments.
593/// This uses the StackPush and Call messages under the hood.
594pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
595where
596    I: IntoIterator<Item = Value>,
597    I::IntoIter: DoubleEndedIterator,
598{
599    let mut num_args = 0_usize;
600    for arg in args.into_iter().rev() {
601        num_args += 1;
602        request_stack_push(co, arg).await;
603    }
604
605    debug_assert!(num_args > 0, "call_with called with an empty list of args");
606
607    while num_args > 0 {
608        callable = request_call(co, callable).await;
609        num_args -= 1;
610    }
611
612    callable
613}
614
615pub async fn request_string_coerce(
616    co: &GenCo,
617    val: Value,
618    kind: CoercionKind,
619) -> Result<NixString, CatchableErrorKind> {
620    match val {
621        Value::String(s) => Ok(s),
622        _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
623            VMResponse::Value(Value::Catchable(c)) => Err(*c),
624            VMResponse::Value(value) => Ok(value
625                .to_contextful_str()
626                .expect("coerce_to_string always returns a string")),
627            msg => panic!(
628                "Snix bug: VM responded with incorrect generator message: {}",
629                msg
630            ),
631        },
632    }
633}
634
635/// Deep-force any value and return the evaluated result from the VM.
636pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
637    match co.yield_(VMRequest::DeepForceValue(val)).await {
638        VMResponse::Value(value) => value,
639        msg => panic!(
640            "Snix bug: VM responded with incorrect generator message: {}",
641            msg
642        ),
643    }
644}
645
646/// Ask the VM to compare two values for equality.
647pub(crate) async fn check_equality(
648    co: &GenCo,
649    a: Value,
650    b: Value,
651    ptr_eq: PointerEquality,
652) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
653    match co
654        .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
655        .await
656    {
657        VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
658        VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
659        msg => panic!(
660            "Snix bug: VM responded with incorrect generator message: {}",
661            msg
662        ),
663    }
664}
665
666/// Emit a fully constructed runtime warning.
667pub(crate) async fn emit_warning(co: &GenCo, warning: EvalWarning) {
668    match co.yield_(VMRequest::EmitWarning(warning)).await {
669        VMResponse::Empty => {}
670        msg => panic!(
671            "Snix bug: VM responded with incorrect generator message: {}",
672            msg
673        ),
674    }
675}
676
677/// Emit a runtime warning with the span of the current generator.
678pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
679    match co.yield_(VMRequest::EmitWarningKind(kind)).await {
680        VMResponse::Empty => {}
681        msg => panic!(
682            "Snix bug: VM responded with incorrect generator message: {}",
683            msg
684        ),
685    }
686}
687
688/// Request that the VM enter the given lambda.
689pub(crate) async fn request_enter_lambda(
690    co: &GenCo,
691    lambda: Rc<Lambda>,
692    upvalues: Rc<Upvalues>,
693    span: Span,
694) -> Value {
695    let msg = VMRequest::EnterLambda {
696        lambda,
697        upvalues,
698        span,
699    };
700
701    match co.yield_(msg).await {
702        VMResponse::Value(value) => value,
703        msg => panic!(
704            "Snix bug: VM responded with incorrect generator message: {}",
705            msg
706        ),
707    }
708}
709
710/// Request a lookup in the VM's import cache.
711pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
712    match co.yield_(VMRequest::ImportCacheLookup(path)).await {
713        VMResponse::Value(value) => Some(value),
714        VMResponse::Empty => None,
715        msg => panic!(
716            "Snix bug: VM responded with incorrect generator message: {}",
717            msg
718        ),
719    }
720}
721
722/// Request that the VM populate its input cache for the given path.
723pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
724    match co.yield_(VMRequest::ImportCachePut(path, value)).await {
725        VMResponse::Empty => {}
726        msg => panic!(
727            "Snix bug: VM responded with incorrect generator message: {}",
728            msg
729        ),
730    }
731}
732
733/// Request that the VM import the given path.
734pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
735    match co.yield_(VMRequest::PathImport(path)).await {
736        VMResponse::Path(path) => path,
737        msg => panic!(
738            "Snix bug: VM responded with incorrect generator message: {}",
739            msg
740        ),
741    }
742}
743
744/// Request that the VM open a [std::io::Read] for the specified file.
745pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
746    match co.yield_(VMRequest::OpenFile(path)).await {
747        VMResponse::Reader(value) => value,
748        msg => panic!(
749            "Snix bug: VM responded with incorrect generator message: {}",
750            msg
751        ),
752    }
753}
754
755#[cfg_attr(not(feature = "impure"), allow(unused))]
756pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
757    match co.yield_(VMRequest::PathExists(path)).await {
758        VMResponse::Value(value) => value,
759        msg => panic!(
760            "Snix bug: VM responded with incorrect generator message: {}",
761            msg
762        ),
763    }
764}
765
766#[cfg_attr(not(feature = "impure"), allow(unused))]
767pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
768    match co.yield_(VMRequest::ReadDir(path)).await {
769        VMResponse::Directory(dir) => dir,
770        msg => panic!(
771            "Snix bug: VM responded with incorrect generator message: {}",
772            msg
773        ),
774    }
775}
776
777pub(crate) async fn request_span(co: &GenCo) -> Span {
778    match co.yield_(VMRequest::Span).await {
779        VMResponse::Span(span) => span,
780        msg => panic!(
781            "Snix bug: VM responded with incorrect generator message: {}",
782            msg
783        ),
784    }
785}
786
787#[cfg_attr(not(feature = "impure"), allow(unused))]
788pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileType {
789    match co.yield_(VMRequest::ReadFileType(path)).await {
790        VMResponse::FileType(file_type) => file_type,
791        msg => panic!(
792            "Snix bug: VM responded with incorrect generator message: {}",
793            msg
794        ),
795    }
796}
797
798/// Call the given value as if it was an attribute set containing a functor. The
799/// arguments must already be prepared on the stack when a generator frame from
800/// this function is invoked.
801///
802pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
803    let attrs = value.to_attrs()?;
804
805    match attrs.select("__functor") {
806        None => Err(ErrorKind::NotCallable("set without `__functor_` attribute")),
807        Some(functor) => {
808            // The functor receives the set itself as its first argument and
809            // needs to be called with it.
810            let functor = request_force(&co, functor.clone()).await;
811            let primed = request_call_with(&co, functor, [value]).await;
812            Ok(request_call(&co, primed).await)
813        }
814    }
815}