Skip to main content

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