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