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
355                                .upvalues
356                                .get_from_with_stack(idx)
357                                .expect("Snix bug: upvalue not found on stack");
358                            self.enqueue_generator("force", span, |co| {
359                                value.force_owned_genco(co, span)
360                            });
361
362                            return Ok(false);
363                        }
364
365                        VMRequest::NixEquality(values, ptr_eq) => {
366                            let values = *values;
367                            self.reenqueue_generator(name, span, generator);
368                            self.enqueue_generator("nix_eq", span, |co| {
369                                values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
370                            });
371                            return Ok(false);
372                        }
373
374                        VMRequest::StringCoerce(val, kind) => {
375                            self.reenqueue_generator(name, span, generator);
376                            self.enqueue_generator("coerce_to_string", span, |co| {
377                                val.coerce_to_string(co, kind, span)
378                            });
379                            return Ok(false);
380                        }
381
382                        VMRequest::Call(callable) => {
383                            self.reenqueue_generator(name, span, generator);
384                            self.call_value(span, None, callable)?;
385                            return Ok(false);
386                        }
387
388                        VMRequest::EnterLambda {
389                            lambda,
390                            upvalues,
391                            span,
392                        } => {
393                            self.reenqueue_generator(name, span, generator);
394
395                            self.frames.push(Frame::CallFrame {
396                                span,
397                                call_frame: CallFrame {
398                                    lambda,
399                                    upvalues,
400                                    ip: CodeIdx(0),
401                                    stack_offset: self.stack.len(),
402                                },
403                            });
404
405                            return Ok(false);
406                        }
407
408                        VMRequest::EmitWarning(warning) => {
409                            self.push_warning(warning);
410                            message = VMResponse::Empty;
411                        }
412
413                        VMRequest::EmitWarningKind(kind) => {
414                            self.emit_warning(kind);
415                            message = VMResponse::Empty;
416                        }
417
418                        VMRequest::ImportCacheLookup(path) => {
419                            if let Some(cached) = self.import_cache.get(&path) {
420                                message = VMResponse::Value(cached.clone());
421                            } else {
422                                message = VMResponse::Empty;
423                            }
424                        }
425
426                        VMRequest::ImportCachePut(path, value) => {
427                            self.import_cache.insert(path, value);
428                            message = VMResponse::Empty;
429                        }
430
431                        VMRequest::PathImport(path) => {
432                            let imported = if let Some(p) = self.path_import_cache.get(&path) {
433                                p.to_owned()
434                            } else {
435                                let imported = self
436                                    .io_handle
437                                    .as_ref()
438                                    .import_path(&path)
439                                    .map_err(|e| ErrorKind::IO {
440                                        path: Some(path.to_owned()),
441                                        error: e.into(),
442                                    })
443                                    .with_span(span, self)?;
444
445                                self.path_import_cache.insert(path, imported.clone());
446                                imported
447                            };
448
449                            message = VMResponse::Path(imported);
450                        }
451
452                        VMRequest::OpenFile(path) => {
453                            let reader = self
454                                .io_handle
455                                .as_ref()
456                                .open(&path)
457                                .map_err(|e| ErrorKind::IO {
458                                    path: Some(path),
459                                    error: e.into(),
460                                })
461                                .with_span(span, self)?;
462
463                            message = VMResponse::Reader(reader)
464                        }
465
466                        VMRequest::PathExists(path) => {
467                            let exists = self
468                                .io_handle
469                                .as_ref()
470                                .path_exists(&path)
471                                .map_err(|e| ErrorKind::IO {
472                                    path: Some(path),
473                                    error: e.into(),
474                                })
475                                .map(Value::Bool)
476                                .with_span(span, self)?;
477
478                            message = VMResponse::Value(exists);
479                        }
480
481                        VMRequest::ReadDir(path) => {
482                            let dir = self
483                                .io_handle
484                                .as_ref()
485                                .read_dir(&path)
486                                .map_err(|e| ErrorKind::IO {
487                                    path: Some(path),
488                                    error: e.into(),
489                                })
490                                .with_span(span, self)?;
491                            message = VMResponse::Directory(dir);
492                        }
493
494                        VMRequest::Span => {
495                            message = VMResponse::Span(self.reasonable_span);
496                        }
497
498                        VMRequest::TryForce(value) => {
499                            self.try_eval_frames.push(frame_id);
500                            self.reenqueue_generator(name, span, generator);
501
502                            debug_assert!(
503                                self.frames.len() == frame_id + 1,
504                                "generator should be reenqueued with the same frame ID"
505                            );
506
507                            self.enqueue_generator("force", span, |co| {
508                                value.force_owned_genco(co, span)
509                            });
510                            return Ok(false);
511                        }
512
513                        VMRequest::ReadFileType(path) => {
514                            let file_type = self
515                                .io_handle
516                                .as_ref()
517                                .file_type(&path)
518                                .map_err(|e| ErrorKind::IO {
519                                    path: Some(path),
520                                    error: e.into(),
521                                })
522                                .with_span(span, self)?;
523
524                            message = VMResponse::FileType(file_type);
525                        }
526                        VMRequest::GetEnv(key) => {
527                            let env = self.io_handle.as_ref().get_env(&key).unwrap_or_default();
528
529                            message = VMResponse::Env(env);
530                        }
531                    }
532                }
533
534                // Generator has completed, and its result value should
535                // be left on the stack.
536                genawaiter::GeneratorState::Complete(result) => {
537                    let value = result.with_span(span, self)?;
538                    self.stack.push(value);
539                    return Ok(true);
540                }
541            }
542        }
543    }
544}
545
546pub type GenCo = Co<VMRequest, VMResponse>;
547
548// -- Implementation of concrete generator use-cases.
549
550/// Request that the VM place the given value on its stack.
551pub async fn request_stack_push(co: &GenCo, val: Value) {
552    match co.yield_(VMRequest::StackPush(val)).await {
553        VMResponse::Empty => {}
554        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
555    }
556}
557
558/// Request that the VM pop a value from the stack and return it to the
559/// generator.
560pub async fn request_stack_pop(co: &GenCo) -> Value {
561    match co.yield_(VMRequest::StackPop).await {
562        VMResponse::Value(value) => value,
563        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
564    }
565}
566
567/// Force any value and return the evaluated result from the VM.
568pub async fn request_force(co: &GenCo, val: Value) -> Value {
569    if let Value::Thunk(_) = val {
570        match co.yield_(VMRequest::ForceValue(val)).await {
571            VMResponse::Value(value) => value,
572            msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
573        }
574    } else {
575        val
576    }
577}
578
579/// Force a value
580pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
581    if let Value::Thunk(_) = val {
582        match co.yield_(VMRequest::TryForce(val)).await {
583            VMResponse::Value(value) => value,
584            msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
585        }
586    } else {
587        val
588    }
589}
590
591/// Call the given value as a callable. The argument(s) must already be prepared
592/// on the stack.
593pub async fn request_call(co: &GenCo, val: Value) -> Value {
594    let val = request_force(co, val).await;
595    match co.yield_(VMRequest::Call(val)).await {
596        VMResponse::Value(value) => value,
597        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
598    }
599}
600
601/// Helper function to call the given value with the provided list of arguments.
602/// This uses the StackPush and Call messages under the hood.
603pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
604where
605    I: IntoIterator<Item = Value>,
606    I::IntoIter: DoubleEndedIterator,
607{
608    let mut num_args = 0_usize;
609    for arg in args.into_iter().rev() {
610        num_args += 1;
611        request_stack_push(co, arg).await;
612    }
613
614    debug_assert!(num_args > 0, "call_with called with an empty list of args");
615
616    while num_args > 0 {
617        callable = request_call(co, callable).await;
618        num_args -= 1;
619    }
620
621    callable
622}
623
624pub async fn request_string_coerce(
625    co: &GenCo,
626    val: Value,
627    kind: CoercionKind,
628) -> Result<NixString, CatchableErrorKind> {
629    match val {
630        Value::String(s) => Ok(s),
631        _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
632            VMResponse::Value(Value::Catchable(c)) => Err(*c),
633            VMResponse::Value(value) => Ok(value
634                .to_contextful_str()
635                .expect("coerce_to_string always returns a string")),
636            msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
637        },
638    }
639}
640
641/// Deep-force any value and return the evaluated result from the VM.
642pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
643    match co.yield_(VMRequest::DeepForceValue(val)).await {
644        VMResponse::Value(value) => value,
645        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
646    }
647}
648
649/// Ask the VM to compare two values for equality.
650pub(crate) async fn check_equality(
651    co: &GenCo,
652    a: Value,
653    b: Value,
654    ptr_eq: PointerEquality,
655) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
656    match co
657        .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
658        .await
659    {
660        VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
661        VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
662        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
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!("Snix bug: VM responded with incorrect generator message: {msg}"),
671    }
672}
673
674/// Emit a runtime warning with the span of the current generator.
675pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
676    match co.yield_(VMRequest::EmitWarningKind(kind)).await {
677        VMResponse::Empty => {}
678        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
679    }
680}
681
682/// Request that the VM enter the given lambda.
683pub(crate) async fn request_enter_lambda(
684    co: &GenCo,
685    lambda: Rc<Lambda>,
686    upvalues: Rc<Upvalues>,
687    span: Span,
688) -> Value {
689    let msg = VMRequest::EnterLambda {
690        lambda,
691        upvalues,
692        span,
693    };
694
695    match co.yield_(msg).await {
696        VMResponse::Value(value) => value,
697        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
698    }
699}
700
701/// Request a lookup in the VM's import cache.
702pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
703    match co.yield_(VMRequest::ImportCacheLookup(path)).await {
704        VMResponse::Value(value) => Some(value),
705        VMResponse::Empty => None,
706        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
707    }
708}
709
710/// Request that the VM populate its input cache for the given path.
711pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
712    match co.yield_(VMRequest::ImportCachePut(path, value)).await {
713        VMResponse::Empty => {}
714        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
715    }
716}
717
718/// Request that the VM import the given path.
719pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
720    match co.yield_(VMRequest::PathImport(path)).await {
721        VMResponse::Path(path) => path,
722        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
723    }
724}
725
726/// Request that the VM open a [std::io::Read] for the specified file.
727pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
728    match co.yield_(VMRequest::OpenFile(path)).await {
729        VMResponse::Reader(value) => value,
730        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
731    }
732}
733
734#[cfg_attr(not(feature = "impure"), allow(unused))]
735pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
736    match co.yield_(VMRequest::PathExists(path)).await {
737        VMResponse::Value(value) => value,
738        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
739    }
740}
741
742#[cfg_attr(not(feature = "impure"), allow(unused))]
743pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
744    match co.yield_(VMRequest::ReadDir(path)).await {
745        VMResponse::Directory(dir) => dir,
746        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
747    }
748}
749
750pub(crate) async fn request_span(co: &GenCo) -> Span {
751    match co.yield_(VMRequest::Span).await {
752        VMResponse::Span(span) => span,
753        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
754    }
755}
756
757#[cfg_attr(not(feature = "impure"), allow(unused))]
758pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileType {
759    match co.yield_(VMRequest::ReadFileType(path)).await {
760        VMResponse::FileType(file_type) => file_type,
761        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
762    }
763}
764
765#[cfg_attr(not(feature = "impure"), allow(unused))]
766pub(crate) async fn request_get_env(co: &GenCo, key: OsString) -> OsString {
767    match co.yield_(VMRequest::GetEnv(key)).await {
768        VMResponse::Env(env) => env,
769        msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
770    }
771}
772
773/// Call the given value as if it was an attribute set containing a functor. The
774/// arguments must already be prepared on the stack when a generator frame from
775/// this function is invoked.
776///
777pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
778    let attrs = value.to_attrs()?;
779
780    match attrs.select("__functor") {
781        None => Err(ErrorKind::NotCallable("set without `__functor_` attribute")),
782        Some(functor) => {
783            // The functor receives the set itself as its first argument and
784            // needs to be called with it.
785            let functor = request_force(&co, functor.clone()).await;
786            let primed = request_call_with(&co, functor, [value]).await;
787            Ok(request_call(&co, primed).await)
788        }
789    }
790}