snix_eval/compiler/
mod.rs

1//! This module implements a compiler for compiling the rnix AST
2//! representation to Snix bytecode.
3//!
4//! A note on `unwrap()`: This module contains a lot of calls to
5//! `unwrap()` or `expect(...)` on data structures returned by `rnix`.
6//! The reason for this is that rnix uses the same data structures to
7//! represent broken and correct ASTs, so all typed AST variants have
8//! the ability to represent an incorrect node.
9//!
10//! However, at the time that the AST is passed to the compiler we
11//! have verified that `rnix` considers the code to be correct, so all
12//! variants are fulfilled. In cases where the invariant is guaranteed
13//! by the code in this module, `debug_assert!` has been used to catch
14//! mistakes early during development.
15
16mod bindings;
17mod import;
18mod optimiser;
19mod scope;
20
21use codemap::Span;
22use rnix::ast::{self, AstToken, InterpolPart, PathContent};
23use rustc_hash::FxHashMap;
24use smol_str::SmolStr;
25use std::collections::BTreeMap;
26use std::path::PathBuf;
27use std::rc::{Rc, Weak};
28
29use crate::SourceCode;
30use crate::chunk::Chunk;
31use crate::errors::{Error, ErrorKind, EvalResult};
32use crate::observer::{CompilerObserver, OptionalCompilerObserver};
33use crate::opcode::{CodeIdx, Op, Position, UpvalueIdx};
34use crate::spans::ToSpan;
35use crate::upvalues::UpvalueData;
36use crate::value::{Closure, Formals, Lambda, NixAttrs, Thunk, Value};
37use crate::warnings::{EvalWarning, WarningKind};
38use crate::{CoercionKind, NixString};
39
40use self::scope::{LocalIdx, LocalPosition, Scope, Upvalue, UpvalueKind};
41
42/// Represents the result of compiling a piece of Nix code. If
43/// compilation was successful, the resulting bytecode can be passed
44/// to the VM.
45pub struct CompilationOutput {
46    pub lambda: Rc<Lambda>,
47    pub warnings: Vec<EvalWarning>,
48    pub errors: Vec<Error>,
49}
50
51/// Represents the lambda currently being compiled.
52struct LambdaCtx {
53    lambda: Lambda,
54    scope: Scope,
55    captures_with_stack: bool,
56}
57
58impl LambdaCtx {
59    fn new() -> Self {
60        LambdaCtx {
61            lambda: Lambda::default(),
62            scope: Default::default(),
63            captures_with_stack: false,
64        }
65    }
66
67    fn inherit(&self) -> Self {
68        LambdaCtx {
69            lambda: Lambda::default(),
70            scope: self.scope.inherit(),
71            captures_with_stack: false,
72        }
73    }
74}
75
76/// When compiling functions with an argument attribute set destructuring pattern,
77/// we need to do multiple passes over the declared formal arguments when setting
78/// up their local bindings (similarly to `let … in` expressions and recursive
79/// attribute sets. For this purpose, this struct is used to represent the two
80/// kinds of formal arguments:
81///
82/// - `TrackedFormal::NoDefault` is always required and causes an evaluation error
83///   if the corresponding attribute is missing in a function call.
84/// - `TrackedFormal::WithDefault` may be missing in the passed attribute set—
85///   in which case a `default_expr` will be evaluated and placed in the formal
86///   argument's local variable slot.
87enum TrackedFormal {
88    NoDefault {
89        local_idx: LocalIdx,
90        pattern_entry: ast::PatEntry,
91    },
92    WithDefault {
93        local_idx: LocalIdx,
94        /// Extra phantom local used for coordinating runtime dispatching not observable to
95        /// the language user. Detailed description in `compile_param_pattern()`.
96        finalise_request_idx: LocalIdx,
97        default_expr: ast::Expr,
98        pattern_entry: ast::PatEntry,
99    },
100}
101
102impl TrackedFormal {
103    fn pattern_entry(&self) -> &ast::PatEntry {
104        match self {
105            TrackedFormal::NoDefault { pattern_entry, .. } => pattern_entry,
106            TrackedFormal::WithDefault { pattern_entry, .. } => pattern_entry,
107        }
108    }
109    fn local_idx(&self) -> LocalIdx {
110        match self {
111            TrackedFormal::NoDefault { local_idx, .. } => *local_idx,
112            TrackedFormal::WithDefault { local_idx, .. } => *local_idx,
113        }
114    }
115}
116
117/// The map of globally available functions and other values that
118/// should implicitly be resolvable in the global scope.
119pub type GlobalsMap = FxHashMap<&'static str, Value>;
120
121/// Set of builtins that (if they exist) should be made available in
122/// the global scope, meaning that they can be accessed not just
123/// through `builtins.<name>`, but directly as `<name>`. This is not
124/// configurable, it is based on what Nix 2.3 exposed.
125const GLOBAL_BUILTINS: &[&str] = &[
126    "abort",
127    "baseNameOf",
128    "derivation",
129    "derivationStrict",
130    "dirOf",
131    "fetchGit",
132    "fetchMercurial",
133    "fetchTarball",
134    "fromTOML",
135    "import",
136    "isNull",
137    "map",
138    "placeholder",
139    "removeAttrs",
140    "scopedImport",
141    "throw",
142    "toString",
143    "__curPos",
144];
145
146pub struct Compiler<'source, 'observer> {
147    contexts: Vec<LambdaCtx>,
148    warnings: Vec<EvalWarning>,
149    errors: Vec<Error>,
150    root_dir: PathBuf,
151
152    /// Carries all known global tokens; the full set of which is
153    /// created when the compiler is invoked.
154    ///
155    /// Each global has an associated token, which when encountered as
156    /// an identifier is resolved against the scope poisoning logic,
157    /// and a function that should emit code for the token.
158    globals: Rc<GlobalsMap>,
159
160    /// Reference to the struct holding all of the source code, which
161    /// is used for error creation.
162    source: &'source SourceCode,
163
164    /// File reference in the source map for the current file, which
165    /// is used for creating spans.
166    file: &'source codemap::File,
167
168    /// Carry an observer for the compilation process, which is called
169    /// whenever a chunk is emitted.
170    observer: OptionalCompilerObserver<'observer>,
171
172    /// Carry a count of nested scopes which have requested the
173    /// compiler not to emit anything. This used for compiling dead
174    /// code branches to catch errors & warnings in them.
175    dead_scope: usize,
176}
177
178impl Compiler<'_, '_> {
179    pub(super) fn span_for<S: ToSpan>(&self, to_span: &S) -> Span {
180        to_span.span_for(self.file)
181    }
182}
183
184/// Compiler construction
185impl<'source, 'observer> Compiler<'source, 'observer> {
186    pub(crate) fn new(
187        location: Option<PathBuf>,
188        globals: Rc<GlobalsMap>,
189        env: Option<&FxHashMap<SmolStr, Value>>,
190        source: &'source SourceCode,
191        file: &'source codemap::File,
192        observer: OptionalCompilerObserver<'observer>,
193    ) -> EvalResult<Self> {
194        let mut root_dir = match location {
195            Some(dir) if cfg!(target_arch = "wasm32") || dir.is_absolute() => Ok(dir),
196            _ => {
197                let current_dir = std::env::current_dir().map_err(|e| {
198                    Error::new(
199                        ErrorKind::RelativePathResolution(format!(
200                            "could not determine current directory: {e}"
201                        )),
202                        file.span,
203                        source.clone(),
204                    )
205                })?;
206                if let Some(dir) = location {
207                    Ok(current_dir.join(dir))
208                } else {
209                    Ok(current_dir)
210                }
211            }
212        }?;
213
214        // If the path passed from the caller points to a file, the
215        // filename itself needs to be truncated as this must point to a
216        // directory.
217        if root_dir.is_file() {
218            root_dir.pop();
219        }
220
221        #[cfg(not(target_arch = "wasm32"))]
222        debug_assert!(root_dir.is_absolute());
223
224        let mut compiler = Self {
225            root_dir,
226            source,
227            file,
228            observer,
229            globals,
230            contexts: vec![LambdaCtx::new()],
231            warnings: vec![],
232            errors: vec![],
233            dead_scope: 0,
234        };
235
236        if let Some(env) = env {
237            compiler.compile_env(env);
238        }
239
240        Ok(compiler)
241    }
242}
243
244// Helper functions for emitting code and metadata to the internal
245// structures of the compiler.
246impl Compiler<'_, '_> {
247    fn context(&self) -> &LambdaCtx {
248        &self.contexts[self.contexts.len() - 1]
249    }
250
251    fn context_mut(&mut self) -> &mut LambdaCtx {
252        let idx = self.contexts.len() - 1;
253        &mut self.contexts[idx]
254    }
255
256    fn chunk(&mut self) -> &mut Chunk {
257        &mut self.context_mut().lambda.chunk
258    }
259
260    fn scope(&self) -> &Scope {
261        &self.context().scope
262    }
263
264    fn scope_mut(&mut self) -> &mut Scope {
265        &mut self.context_mut().scope
266    }
267
268    /// Push a single instruction to the current bytecode chunk and
269    /// track the source span from which it was compiled.
270    fn push_op<T: ToSpan>(&mut self, data: Op, node: &T) -> CodeIdx {
271        if self.dead_scope > 0 {
272            return CodeIdx(0);
273        }
274
275        let span = self.span_for(node);
276        CodeIdx(self.chunk().push_op(data, span))
277    }
278
279    fn push_u8(&mut self, data: u8) {
280        if self.dead_scope > 0 {
281            return;
282        }
283
284        self.chunk().code.push(data);
285    }
286
287    fn push_uvarint(&mut self, data: u64) {
288        if self.dead_scope > 0 {
289            return;
290        }
291
292        self.chunk().push_uvarint(data);
293    }
294
295    fn push_u16(&mut self, data: u16) {
296        if self.dead_scope > 0 {
297            return;
298        }
299
300        self.chunk().push_u16(data);
301    }
302
303    /// Emit a single constant to the current bytecode chunk and track
304    /// the source span from which it was compiled.
305    pub(super) fn emit_constant<T: ToSpan>(&mut self, value: Value, node: &T) {
306        if self.dead_scope > 0 {
307            return;
308        }
309
310        let idx = self.chunk().push_constant(value);
311        self.push_op(Op::Constant, node);
312        self.push_uvarint(idx.0 as u64);
313    }
314
315    /// Emit bytecode for path interpolation parts in reverse order.
316    /// Literals become string constants, interpolations are compiled
317    /// and coerced to strings.
318    pub(super) fn emit_path_ipol_parts<T: ToSpan>(
319        &mut self,
320        slot: LocalIdx,
321        node: &T,
322        parts: impl DoubleEndedIterator<Item = InterpolPart<PathContent>>,
323    ) {
324        for part in parts.rev() {
325            match part {
326                InterpolPart::Interpolation(ipol) => {
327                    self.compile(slot, ipol.expr().unwrap());
328                    self.push_op(Op::CoerceToString, &ipol);
329                    let encoded: u8 = CoercionKind {
330                        strong: false,
331                        import_paths: true,
332                    }
333                    .into();
334                    self.push_u8(encoded);
335                }
336                InterpolPart::Literal(content) => {
337                    self.emit_constant(Value::String(content.text().into()), node);
338                }
339            }
340        }
341    }
342}
343
344// Actual code-emitting AST traversal methods.
345impl Compiler<'_, '_> {
346    fn compile(&mut self, slot: LocalIdx, expr: ast::Expr) {
347        let expr = optimiser::optimise_expr(self, slot, expr);
348
349        match &expr {
350            ast::Expr::Literal(literal) => self.compile_literal(literal),
351            ast::Expr::PathAbs(path) => self.compile_abs_path(slot, path),
352            ast::Expr::PathHome(path) => self.compile_home_path(slot, path),
353            ast::Expr::PathRel(path) => self.compile_rel_path(slot, path),
354            ast::Expr::PathSearch(path) => self.compile_search_path(slot, path),
355            ast::Expr::Str(s) => self.compile_str(slot, s),
356
357            ast::Expr::UnaryOp(op) => self.thunk(slot, op, move |c, s| c.compile_unary_op(s, op)),
358
359            ast::Expr::BinOp(binop) => {
360                self.thunk(slot, binop, move |c, s| c.compile_binop(s, binop))
361            }
362
363            ast::Expr::HasAttr(has_attr) => {
364                self.thunk(slot, has_attr, move |c, s| c.compile_has_attr(s, has_attr))
365            }
366
367            ast::Expr::List(list) => self.thunk(slot, list, move |c, s| c.compile_list(s, list)),
368
369            ast::Expr::AttrSet(attrs) => {
370                self.thunk(slot, attrs, move |c, s| c.compile_attr_set(s, attrs))
371            }
372
373            ast::Expr::Select(select) => {
374                self.thunk(slot, select, move |c, s| c.compile_select(s, select))
375            }
376
377            ast::Expr::Assert(assert) => {
378                self.thunk(slot, assert, move |c, s| c.compile_assert(s, assert))
379            }
380            ast::Expr::IfElse(if_else) => {
381                self.thunk(slot, if_else, move |c, s| c.compile_if_else(s, if_else))
382            }
383
384            ast::Expr::LetIn(let_in) => {
385                self.thunk(slot, let_in, move |c, s| c.compile_let_in(s, let_in))
386            }
387
388            ast::Expr::Ident(ident) => self.compile_ident(slot, ident),
389            ast::Expr::With(with) => self.thunk(slot, with, |c, s| c.compile_with(s, with)),
390            ast::Expr::Lambda(lambda) => self.thunk(slot, lambda, move |c, s| {
391                c.compile_lambda_or_thunk(false, s, lambda, |c, s| c.compile_lambda(s, lambda))
392            }),
393            ast::Expr::Apply(apply) => {
394                self.thunk(slot, apply, move |c, s| c.compile_apply(s, apply))
395            }
396
397            // Parenthesized expressions are simply unwrapped, leaving
398            // their value on the stack.
399            ast::Expr::Paren(paren) => self.compile(slot, paren.expr().unwrap()),
400
401            ast::Expr::LegacyLet(legacy_let) => self.thunk(slot, legacy_let, move |c, s| {
402                c.compile_legacy_let(s, legacy_let)
403            }),
404
405            ast::Expr::CurPos(curpos) => self.compile_cur_pos(curpos),
406
407            ast::Expr::Root(_) => unreachable!("there cannot be more than one root"),
408            ast::Expr::Error(_) => unreachable!("compile is only called on validated trees"),
409        }
410    }
411
412    /// Compiles an expression, but does not emit any code for it as
413    /// it is considered dead. This will still catch errors and
414    /// warnings in that expression.
415    ///
416    /// A warning about the that code being dead is assumed to already be
417    /// emitted by the caller of this.
418    fn compile_dead_code(&mut self, slot: LocalIdx, node: ast::Expr) {
419        self.dead_scope += 1;
420        self.compile(slot, node);
421        self.dead_scope -= 1;
422    }
423
424    fn compile_literal(&mut self, node: &ast::Literal) {
425        let value = match node.kind() {
426            ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()),
427            ast::LiteralKind::Integer(i) => match i.value() {
428                Ok(v) => Value::Integer(v),
429                Err(err) => return self.emit_error(node, err.into()),
430            },
431
432            ast::LiteralKind::Uri(u) => {
433                self.emit_warning(node, WarningKind::DeprecatedLiteralURL);
434                Value::from(u.syntax().text())
435            }
436        };
437
438        self.emit_constant(value, node);
439    }
440
441    fn compile_abs_path(&mut self, slot: LocalIdx, node: &ast::PathAbs) {
442        let parts = node.parts();
443
444        if is_interpolated_path(&parts) {
445            self.thunk(slot, node, move |c, s| {
446                let len = parts.len();
447                c.emit_path_ipol_parts(s, node, parts.into_iter());
448                c.push_op(Op::InterpolatePath, node);
449                c.push_uvarint(len as u64);
450            });
451            return;
452        }
453
454        // TODO: Use https://github.com/rust-lang/rfcs/issues/2208
455        // once it is available
456        let path = PathBuf::from(node.to_string());
457        let value = Value::Path(Box::new(crate::value::canon_path(path)));
458        self.emit_constant(value, node);
459    }
460
461    fn compile_home_path(&mut self, slot: LocalIdx, node: &ast::PathHome) {
462        let parts = node.parts();
463        let home_subpath = match &parts[0] {
464            ast::InterpolPart::Literal(part) => &part.text()[2..].to_string(),
465            _ => {
466                // It can't because it always starts with `~/` and rnix
467                // returns it as a literal
468                unreachable!("a home path can't start with interpolation")
469            }
470        };
471
472        if is_interpolated_path(&parts) {
473            self.thunk(slot, node, move |c, s| {
474                let len = parts.len();
475                c.emit_path_ipol_parts(s, node, parts.into_iter().skip(1));
476                c.emit_constant(Value::UnresolvedPath(Box::new(home_subpath.into())), node);
477                c.push_op(Op::ResolveHomePath, node);
478
479                c.push_op(Op::InterpolatePath, node);
480                c.push_uvarint(len as u64);
481            });
482            return;
483        }
484
485        self.emit_constant(Value::UnresolvedPath(Box::new(home_subpath.into())), node);
486        self.push_op(Op::ResolveHomePath, node);
487    }
488
489    fn compile_rel_path(&mut self, slot: LocalIdx, node: &ast::PathRel) {
490        let parts = node.parts();
491        let abs = match &parts[0] {
492            ast::InterpolPart::Literal(part) => self.root_dir.join(part.text()),
493            _ => {
494                unreachable!("a relative path can't start with interpolation");
495            }
496        };
497
498        if is_interpolated_path(&parts) {
499            self.thunk(slot, node, move |c, s| {
500                let len = parts.len();
501                c.emit_path_ipol_parts(s, node, parts.into_iter().skip(1));
502                c.emit_constant(Value::Path(abs.into()), node);
503
504                c.push_op(Op::InterpolatePath, node);
505                c.push_uvarint(len as u64);
506            });
507            return;
508        }
509
510        let value = Value::Path(Box::new(crate::value::canon_path(abs)));
511        self.emit_constant(value, node);
512    }
513
514    fn compile_search_path(&mut self, slot: LocalIdx, node: &ast::PathSearch) {
515        let raw_path = node.to_string();
516        let path = &raw_path[1..(raw_path.len() - 1)];
517        // Make a thunk to resolve the path (without using `findFile`, at least for now?)
518        self.thunk(slot, node, move |c, _| {
519            c.emit_constant(Value::UnresolvedPath(Box::new(path.into())), node);
520            c.push_op(Op::FindFile, node);
521        });
522    }
523
524    /// Helper that compiles the given string parts strictly. The caller
525    /// (`compile_str`) needs to figure out if the result of compiling this
526    /// needs to be thunked or not.
527    fn compile_str_parts(
528        &mut self,
529        slot: LocalIdx,
530        parent_node: &ast::Str,
531        parts: Vec<ast::InterpolPart<String>>,
532    ) {
533        // The string parts are produced in literal order, however
534        // they need to be reversed on the stack in order to
535        // efficiently create the real string in case of
536        // interpolation.
537        for part in parts.iter().rev() {
538            match part {
539                // Interpolated expressions are compiled as normal and
540                // dealt with by the VM before being assembled into
541                // the final string. We need to coerce them here,
542                // so OpInterpolate definitely has a string to consume.
543                ast::InterpolPart::Interpolation(ipol) => {
544                    self.compile(slot, ipol.expr().unwrap());
545                    // implicitly forces as well
546                    self.push_op(Op::CoerceToString, ipol);
547
548                    let encoded: u8 = CoercionKind {
549                        strong: false,
550                        import_paths: true,
551                    }
552                    .into();
553
554                    self.push_u8(encoded);
555                }
556
557                ast::InterpolPart::Literal(lit) => {
558                    self.emit_constant(Value::from(lit.as_str()), parent_node);
559                }
560            }
561        }
562
563        if parts.len() != 1 {
564            self.push_op(Op::Interpolate, parent_node);
565            self.push_uvarint(parts.len() as u64);
566        }
567    }
568
569    fn compile_str(&mut self, slot: LocalIdx, node: &ast::Str) {
570        let parts = node.normalized_parts();
571
572        // We need to thunk string expressions if they are the result of
573        // interpolation. A string that only consists of a single part (`"${foo}"`)
574        // can't desugar to the enclosed expression (`foo`) because we need to
575        // coerce the result to a string value. This would require forcing the
576        // value of the inner expression, so we need to wrap it in another thunk.
577        if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) {
578            self.thunk(slot, node, move |c, s| {
579                c.compile_str_parts(s, node, parts);
580            });
581        } else {
582            self.compile_str_parts(slot, node, parts);
583        }
584    }
585
586    fn compile_unary_op(&mut self, slot: LocalIdx, op: &ast::UnaryOp) {
587        self.compile(slot, op.expr().unwrap());
588        self.emit_force(op);
589
590        let opcode = match op.operator().unwrap() {
591            ast::UnaryOpKind::Invert => Op::Invert,
592            ast::UnaryOpKind::Negate => Op::Negate,
593        };
594
595        self.push_op(opcode, op);
596    }
597
598    fn compile_binop(&mut self, slot: LocalIdx, op: &ast::BinOp) {
599        use ast::BinOpKind;
600
601        match op.operator().unwrap() {
602            // Short-circuiting and other strange operators, which are
603            // under the same node type as NODE_BIN_OP, but need to be
604            // handled separately (i.e. before compiling the expressions
605            // used for standard binary operators).
606            BinOpKind::And => return self.compile_and(slot, op),
607            BinOpKind::Or => return self.compile_or(slot, op),
608            BinOpKind::Implication => return self.compile_implication(slot, op),
609
610            // Pipe operators. Any introduction should be properly
611            // feature-flagged, due to its experimental status.
612            BinOpKind::PipeRight | BinOpKind::PipeLeft => {
613                return self.emit_error(
614                    op,
615                    ErrorKind::NotImplemented("pipe operators not implemented"),
616                );
617            }
618
619            _ => {}
620        };
621
622        // For all other operators, the two values need to be left on
623        // the stack in the correct order before pushing the
624        // instruction for the operation itself.
625        self.compile(slot, op.lhs().unwrap());
626        self.emit_force(&op.lhs().unwrap());
627
628        self.compile(slot, op.rhs().unwrap());
629        self.emit_force(&op.rhs().unwrap());
630
631        match op.operator().unwrap() {
632            BinOpKind::Add => self.push_op(Op::Add, op),
633            BinOpKind::Sub => self.push_op(Op::Sub, op),
634            BinOpKind::Mul => self.push_op(Op::Mul, op),
635            BinOpKind::Div => self.push_op(Op::Div, op),
636            BinOpKind::Update => self.push_op(Op::AttrsUpdate, op),
637            BinOpKind::Equal => self.push_op(Op::Equal, op),
638            BinOpKind::Less => self.push_op(Op::Less, op),
639            BinOpKind::LessOrEq => self.push_op(Op::LessOrEq, op),
640            BinOpKind::More => self.push_op(Op::More, op),
641            BinOpKind::MoreOrEq => self.push_op(Op::MoreOrEq, op),
642            BinOpKind::Concat => self.push_op(Op::Concat, op),
643            BinOpKind::NotEqual => {
644                self.push_op(Op::Equal, op);
645                self.push_op(Op::Invert, op)
646            }
647            BinOpKind::And
648            | BinOpKind::Implication
649            | BinOpKind::Or
650            | BinOpKind::PipeRight
651            | BinOpKind::PipeLeft => {
652                unreachable!()
653            }
654        };
655    }
656
657    fn compile_and(&mut self, slot: LocalIdx, node: &ast::BinOp) {
658        debug_assert!(
659            matches!(node.operator(), Some(ast::BinOpKind::And)),
660            "compile_and called with wrong operator kind: {:?}",
661            node.operator(),
662        );
663
664        // Leave left-hand side value on the stack.
665        self.compile(slot, node.lhs().unwrap());
666        self.emit_force(&node.lhs().unwrap());
667
668        let throw_idx = self.push_op(Op::JumpIfCatchable, node);
669        self.push_u16(0);
670        // If this value is false, jump over the right-hand side - the
671        // whole expression is false.
672        let end_idx = self.push_op(Op::JumpIfFalse, node);
673        self.push_u16(0);
674
675        // Otherwise, remove the previous value and leave the
676        // right-hand side on the stack. Its result is now the value
677        // of the whole expression.
678        self.push_op(Op::Pop, node);
679        self.compile(slot, node.rhs().unwrap());
680        self.emit_force(&node.rhs().unwrap());
681
682        self.patch_jump(end_idx);
683        self.push_op(Op::AssertBool, node);
684        self.patch_jump(throw_idx);
685    }
686
687    fn compile_or(&mut self, slot: LocalIdx, node: &ast::BinOp) {
688        debug_assert!(
689            matches!(node.operator(), Some(ast::BinOpKind::Or)),
690            "compile_or called with wrong operator kind: {:?}",
691            node.operator(),
692        );
693
694        // Leave left-hand side value on the stack
695        self.compile(slot, node.lhs().unwrap());
696        self.emit_force(&node.lhs().unwrap());
697
698        let throw_idx = self.push_op(Op::JumpIfCatchable, node);
699        self.push_u16(0);
700        // Opposite of above: If this value is **true**, we can
701        // short-circuit the right-hand side.
702        let end_idx = self.push_op(Op::JumpIfTrue, node);
703        self.push_u16(0);
704        self.push_op(Op::Pop, node);
705        self.compile(slot, node.rhs().unwrap());
706        self.emit_force(&node.rhs().unwrap());
707
708        self.patch_jump(end_idx);
709        self.push_op(Op::AssertBool, node);
710        self.patch_jump(throw_idx);
711    }
712
713    fn compile_implication(&mut self, slot: LocalIdx, node: &ast::BinOp) {
714        debug_assert!(
715            matches!(node.operator(), Some(ast::BinOpKind::Implication)),
716            "compile_implication called with wrong operator kind: {:?}",
717            node.operator(),
718        );
719
720        // Leave left-hand side value on the stack and invert it.
721        self.compile(slot, node.lhs().unwrap());
722        self.emit_force(&node.lhs().unwrap());
723        let throw_idx = self.push_op(Op::JumpIfCatchable, node);
724        self.push_u16(0);
725        self.push_op(Op::Invert, node);
726
727        // Exactly as `||` (because `a -> b` = `!a || b`).
728        let end_idx = self.push_op(Op::JumpIfTrue, node);
729        self.push_u16(0);
730
731        self.push_op(Op::Pop, node);
732        self.compile(slot, node.rhs().unwrap());
733        self.emit_force(&node.rhs().unwrap());
734
735        self.patch_jump(end_idx);
736        self.push_op(Op::AssertBool, node);
737        self.patch_jump(throw_idx);
738    }
739
740    /// Compile list literals into equivalent bytecode. List
741    /// construction is fairly simple, consisting of pushing code for
742    /// each literal element and an instruction with the element
743    /// count.
744    ///
745    /// The VM, after evaluating the code for each element, simply
746    /// constructs the list from the given number of elements.
747    fn compile_list(&mut self, slot: LocalIdx, node: &ast::List) {
748        let mut count = 0;
749
750        // Open a temporary scope to correctly account for stack items
751        // that exist during the construction.
752        self.scope_mut().begin_scope();
753
754        for item in node.items() {
755            // Start tracing new stack slots from the second list
756            // element onwards. The first list element is located in
757            // the stack slot of the list itself.
758            let item_slot = match count {
759                0 => slot,
760                _ => {
761                    let item_span = self.span_for(&item);
762                    self.scope_mut().declare_phantom(item_span, false)
763                }
764            };
765
766            count += 1;
767            self.compile(item_slot, item);
768            self.scope_mut().mark_initialised(item_slot);
769        }
770
771        self.push_op(Op::List, node);
772        self.push_uvarint(count as u64);
773        self.scope_mut().end_scope();
774    }
775
776    fn compile_attr(&mut self, slot: LocalIdx, node: &ast::Attr) {
777        match node {
778            ast::Attr::Dynamic(dynamic) => {
779                self.compile(slot, dynamic.expr().unwrap());
780                self.emit_force(&dynamic.expr().unwrap());
781            }
782
783            ast::Attr::Str(s) => {
784                self.compile_str(slot, s);
785                self.emit_force(s);
786            }
787
788            ast::Attr::Ident(ident) => self.emit_literal_ident(ident),
789        }
790    }
791
792    fn compile_has_attr(&mut self, slot: LocalIdx, node: &ast::HasAttr) {
793        // Put the attribute set on the stack.
794        self.compile(slot, node.expr().unwrap());
795        self.emit_force(node);
796
797        // Push all path fragments with an operation for fetching the
798        // next nested element, for all fragments except the last one.
799        for (count, fragment) in node.attrpath().unwrap().attrs().enumerate() {
800            if count > 0 {
801                self.push_op(Op::AttrsTrySelect, &fragment);
802                self.emit_force(&fragment);
803            }
804
805            self.compile_attr(slot, &fragment);
806        }
807
808        // After the last fragment, emit the actual instruction that
809        // leaves a boolean on the stack.
810        self.push_op(Op::HasAttr, node);
811    }
812
813    /// When compiling select or select_or expressions, an optimisation is
814    /// possible of compiling the set emitted a constant attribute set by
815    /// immediately replacing it with the actual value.
816    ///
817    /// We take care not to emit an error here, as that would interfere with
818    /// thunking behaviour (there can be perfectly valid Nix code that accesses
819    /// a statically known attribute set that is lacking a key, because that
820    /// thunk is never evaluated). If anything is missing, just inform the
821    /// caller that the optimisation did not take place and move on. We may want
822    /// to emit warnings here in the future.
823    fn optimise_select(&mut self, path: &ast::Attrpath) -> bool {
824        // If compiling the set emitted a constant attribute set, the
825        // associated constant can immediately be replaced with the
826        // actual value.
827        //
828        // We take care not to emit an error here, as that would
829        // interfere with thunking behaviour (there can be perfectly
830        // valid Nix code that accesses a statically known attribute
831        // set that is lacking a key, because that thunk is never
832        // evaluated). If anything is missing, just move on. We may
833        // want to emit warnings here in the future.
834        if let Some((Op::Constant, op_idx)) = self.chunk().last_op() {
835            let (idx, _) = self.chunk().read_uvarint(op_idx + 1);
836            let constant = &mut self.chunk().constants[idx as usize];
837            if let Value::Attrs(attrs) = constant {
838                let mut path_iter = path.attrs();
839
840                // Only do this optimisation if there is a *single*
841                // element in the attribute path. It is extremely
842                // unlikely that we'd have a static nested set.
843                if let (Some(attr), None) = (path_iter.next(), path_iter.next()) {
844                    // Only do this optimisation for statically known attrs.
845                    if let Some(ident) = expr_static_attr_str(&attr)
846                        && let Some(selected_value) = attrs.select(ident.as_bytes())
847                    {
848                        *constant = selected_value.clone();
849                        return true;
850                    }
851                }
852            }
853        }
854
855        false
856    }
857
858    fn compile_select(&mut self, slot: LocalIdx, node: &ast::Select) {
859        let set = node.expr().unwrap();
860        let path = node.attrpath().unwrap();
861
862        if node.or_token().is_some() {
863            return self.compile_select_or(slot, set, path, node.default_expr().unwrap());
864        }
865
866        // Push the set onto the stack
867        self.compile(slot, set.clone());
868        if self.optimise_select(&path) {
869            return;
870        }
871
872        // Compile each key fragment and emit access instructions.
873        //
874        // TODO: multi-select instruction to avoid re-pushing attrs on
875        // nested selects.
876        for fragment in path.attrs() {
877            // Force the current set value.
878            self.emit_force(&set);
879
880            self.compile_attr(slot, &fragment);
881            self.push_op(Op::AttrsSelect, &fragment);
882        }
883    }
884
885    /// Compile an `or` expression into a chunk of conditional jumps.
886    ///
887    /// If at any point during attribute set traversal a key is
888    /// missing, the `OpAttrOrNotFound` instruction will leave a
889    /// special sentinel value on the stack.
890    ///
891    /// After each access, a conditional jump evaluates the top of the
892    /// stack and short-circuits to the default value if it sees the
893    /// sentinel.
894    ///
895    /// Code like `{ a.b = 1; }.a.c or 42` yields this bytecode and
896    /// runtime stack:
897    ///
898    /// ```notrust
899    ///            Bytecode                     Runtime stack
900    ///  ┌────────────────────────────┐   ┌─────────────────────────┐
901    ///  │    ...                     │   │ ...                     │
902    ///  │ 5  OP_ATTRS(1)             │ → │ 5  [ { a.b = 1; }     ] │
903    ///  │ 6  OP_CONSTANT("a")        │ → │ 6  [ { a.b = 1; } "a" ] │
904    ///  │ 7  OP_ATTR_OR_NOT_FOUND    │ → │ 7  [ { b = 1; }       ] │
905    ///  │ 8  JUMP_IF_NOT_FOUND(13)   │ → │ 8  [ { b = 1; }       ] │
906    ///  │ 9  OP_CONSTANT("C")        │ → │ 9  [ { b = 1; } "c"   ] │
907    ///  │ 10 OP_ATTR_OR_NOT_FOUND    │ → │ 10 [ NOT_FOUND        ] │
908    ///  │ 11 JUMP_IF_NOT_FOUND(13)   │ → │ 11 [                  ] │
909    ///  │ 12 JUMP(14)                │   │ ..     jumped over      │
910    ///  │ 13 CONSTANT(42)            │ → │ 12 [ 42 ]               │
911    ///  │ 14 ...                     │   │ ..   ....               │
912    ///  └────────────────────────────┘   └─────────────────────────┘
913    /// ```
914    fn compile_select_or(
915        &mut self,
916        slot: LocalIdx,
917        set: ast::Expr,
918        path: ast::Attrpath,
919        default: ast::Expr,
920    ) {
921        self.compile(slot, set);
922        if self.optimise_select(&path) {
923            return;
924        }
925
926        let mut jumps = vec![];
927
928        for fragment in path.attrs() {
929            self.emit_force(&fragment);
930            self.compile_attr(slot, &fragment.clone());
931            self.push_op(Op::AttrsTrySelect, &fragment);
932            jumps.push(self.push_op(Op::JumpIfNotFound, &fragment));
933            self.push_u16(0);
934        }
935
936        let final_jump = self.push_op(Op::Jump, &path);
937        self.push_u16(0);
938
939        for jump in jumps {
940            self.patch_jump(jump);
941        }
942
943        // Compile the default value expression and patch the final
944        // jump to point *beyond* it.
945        self.compile(slot, default);
946        self.patch_jump(final_jump);
947    }
948
949    /// Compile `assert` expressions using jumping instructions in the VM.
950    ///
951    /// ```notrust
952    ///                        ┌─────────────────────┐
953    ///                        │ 0  [ conditional ]  │
954    ///                        │ 1   JUMP_IF_FALSE  →┼─┐
955    ///                        │ 2  [  main body  ]  │ │ Jump to else body if
956    ///                       ┌┼─3─←     JUMP        │ │ condition is false.
957    ///  Jump over else body  ││ 4   OP_ASSERT_FAIL ←┼─┘
958    ///  if condition is true.└┼─5─→     ...         │
959    ///                        └─────────────────────┘
960    /// ```
961    fn compile_assert(&mut self, slot: LocalIdx, node: &ast::Assert) {
962        // Compile the assertion condition to leave its value on the stack.
963        self.compile(slot, node.condition().unwrap());
964        self.emit_force(&node.condition().unwrap());
965
966        let throw_idx = self.push_op(Op::JumpIfCatchable, node);
967        self.push_u16(0);
968
969        let then_idx = self.push_op(Op::JumpIfFalse, node);
970        self.push_u16(0);
971
972        self.push_op(Op::Pop, node);
973        self.compile(slot, node.body().unwrap());
974
975        let else_idx = self.push_op(Op::Jump, node);
976        self.push_u16(0);
977
978        self.patch_jump(then_idx);
979        self.push_op(Op::Pop, node);
980        self.push_op(Op::AssertFail, &node.condition().unwrap());
981
982        self.patch_jump(else_idx);
983        self.patch_jump(throw_idx);
984    }
985
986    /// Compile conditional expressions using jumping instructions in the VM.
987    ///
988    /// ```notrust
989    ///                        ┌─────────────────────┐
990    ///                        │ 0  [ conditional ]  │
991    ///                        │ 1   JUMP_IF_CATCH  →┼───┐ Jump over else body
992    ///                        │ 2   JUMP_IF_FALSE  →┼─┐ │ if condition is catchable.
993    ///                        │ 3  [  main body  ]  │ │ ← Jump to else body if
994    ///                       ┌┼─4─←     JUMP        │ │ ← condition is false.
995    ///  Jump over else body  ││ 5  [  else body  ] ←┼─┘ │
996    ///  if condition is true.└┼─6─→     ...        ←┼───┘
997    ///                        └─────────────────────┘
998    /// ```
999    fn compile_if_else(&mut self, slot: LocalIdx, node: &ast::IfElse) {
1000        self.compile(slot, node.condition().unwrap());
1001        self.emit_force(&node.condition().unwrap());
1002
1003        let throw_idx = self.push_op(Op::JumpIfCatchable, &node.condition().unwrap());
1004        self.push_u16(0);
1005
1006        let then_idx = self.push_op(Op::JumpIfFalse, &node.condition().unwrap());
1007        self.push_u16(0);
1008
1009        self.push_op(Op::Pop, node); // discard condition value
1010        self.compile(slot, node.body().unwrap());
1011
1012        let else_idx = self.push_op(Op::Jump, node);
1013        self.push_u16(0);
1014
1015        self.patch_jump(then_idx); // patch jump *to* else_body
1016        self.push_op(Op::Pop, node); // discard condition value
1017        self.compile(slot, node.else_body().unwrap());
1018
1019        self.patch_jump(else_idx); // patch jump *over* else body
1020        self.patch_jump(throw_idx); // patch jump *over* else body
1021    }
1022
1023    /// Compile `with` expressions by emitting instructions that
1024    /// pop/remove the indices of attribute sets that are implicitly
1025    /// in scope through `with` on the "with-stack".
1026    fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
1027        self.scope_mut().begin_scope();
1028        // TODO: Detect if the namespace is just an identifier, and
1029        // resolve that directly (thus avoiding duplication on the
1030        // stack).
1031        self.compile(slot, node.namespace().unwrap());
1032
1033        let span = self.span_for(&node.namespace().unwrap());
1034
1035        // The attribute set from which `with` inherits values
1036        // occupies a slot on the stack, but this stack slot is not
1037        // directly accessible. As it must be accounted for to
1038        // calculate correct offsets, what we call a "phantom" local
1039        // is declared here.
1040        let local_idx = self.scope_mut().declare_phantom(span, true);
1041        let with_idx = self.scope().stack_index(local_idx);
1042
1043        self.scope_mut().push_with();
1044
1045        self.push_op(Op::PushWith, &node.namespace().unwrap());
1046        self.push_uvarint(with_idx.0 as u64);
1047
1048        self.compile(slot, node.body().unwrap());
1049
1050        self.push_op(Op::PopWith, node);
1051        self.scope_mut().pop_with();
1052        self.cleanup_scope(node);
1053    }
1054
1055    /// Compiles pattern function arguments, such as `{ a, b }: ...`.
1056    ///
1057    /// These patterns are treated as a special case of locals binding
1058    /// where the attribute set itself is placed on the first stack
1059    /// slot of the call frame (either as a phantom, or named in case
1060    /// of an `@` binding), and the function call sets up the rest of
1061    /// the stack as if the parameters were rewritten into a `let`
1062    /// binding.
1063    ///
1064    /// For example:
1065    ///
1066    /// ```nix
1067    /// ({ a, b ? 2, c ? a * b, ... }@args: <body>)  { a = 10; }
1068    /// ```
1069    ///
1070    /// would be compiled similarly to a binding such as
1071    ///
1072    /// ```nix
1073    /// let args = { a = 10; };
1074    /// in let a = args.a;
1075    ///        b = args.a or 2;
1076    ///        c = args.c or a * b;
1077    ///    in <body>
1078    /// ```
1079    ///
1080    /// However, there are two properties of pattern function arguments that can
1081    /// not be compiled by desugaring in this way:
1082    ///
1083    /// 1. Bindings have to fail if too many arguments are provided. This is
1084    ///    done by emitting a special instruction that checks the set of keys
1085    ///    from a constant containing the expected keys.
1086    /// 2. Formal arguments with a default expression are (as an optimization and
1087    ///    because it is simpler) not wrapped in another thunk, instead compiled
1088    ///    and accessed separately. This means that the default expression may
1089    ///    never make it into the local's stack slot if the argument is provided
1090    ///    by the caller. We need to take this into account and skip any
1091    ///    operations specific to the expression like thunk finalisation in such
1092    ///    cases.
1093    fn compile_param_pattern(&mut self, pattern: &ast::Pattern) -> (Formals, CodeIdx) {
1094        let span = self.span_for(pattern);
1095
1096        let (set_idx, pat_bind_name) = match pattern.pat_bind() {
1097            Some(name) => {
1098                let pat_bind_name = name.ident().unwrap().to_string();
1099                (
1100                    self.declare_local(&name, pat_bind_name.clone()),
1101                    Some(pat_bind_name),
1102                )
1103            }
1104            None => (self.scope_mut().declare_phantom(span, true), None),
1105        };
1106
1107        // At call time, the attribute set is already at the top of the stack.
1108        self.scope_mut().mark_initialised(set_idx);
1109        self.emit_force(pattern);
1110        let throw_idx = self.push_op(Op::JumpIfCatchable, pattern);
1111        self.push_u16(0);
1112
1113        // Evaluation fails on a type error, even if the argument(s) are unused.
1114        self.push_op(Op::AssertAttrs, pattern);
1115
1116        let ellipsis = pattern.ellipsis_token().is_some();
1117        if !ellipsis {
1118            self.push_op(Op::ValidateClosedFormals, pattern);
1119        }
1120
1121        // Similar to `let ... in ...`, we now do multiple passes over
1122        // the bindings to first declare them, then populate them, and
1123        // then finalise any necessary recursion into the scope.
1124        let mut entries: Vec<TrackedFormal> = vec![];
1125        let mut arguments = BTreeMap::default();
1126
1127        for entry in pattern.pat_entries() {
1128            let ident = entry.ident().unwrap();
1129            let idx = self.declare_local(&ident, ident.to_string());
1130
1131            arguments.insert(ident.into(), entry.default().is_some());
1132
1133            if let Some(default_expr) = entry.default() {
1134                entries.push(TrackedFormal::WithDefault {
1135                    local_idx: idx,
1136                    // This phantom is used to track at runtime (!) whether we need to
1137                    // finalise the local's stack slot or not. The relevant instructions are
1138                    // emitted in the second pass where the mechanism is explained as well.
1139                    finalise_request_idx: {
1140                        let span = self.span_for(&default_expr);
1141                        self.scope_mut().declare_phantom(span, false)
1142                    },
1143                    default_expr,
1144                    pattern_entry: entry,
1145                });
1146            } else {
1147                entries.push(TrackedFormal::NoDefault {
1148                    local_idx: idx,
1149                    pattern_entry: entry,
1150                });
1151            }
1152        }
1153
1154        // For each of the bindings, push the set on the stack and
1155        // attempt to select from it.
1156        let stack_idx = self.scope().stack_index(set_idx);
1157        for tracked_formal in entries.iter() {
1158            self.push_op(Op::GetLocal, pattern);
1159            self.push_uvarint(stack_idx.0 as u64);
1160            self.emit_literal_ident(&tracked_formal.pattern_entry().ident().unwrap());
1161
1162            let idx = tracked_formal.local_idx();
1163
1164            // Use the same mechanism as `compile_select_or` if a
1165            // default value was provided, or simply select otherwise.
1166            match tracked_formal {
1167                TrackedFormal::WithDefault {
1168                    default_expr,
1169                    pattern_entry,
1170                    ..
1171                } => {
1172                    // The tricky bit about compiling a formal argument with a default value
1173                    // is that the default may be a thunk that may depend on the value of
1174                    // other formal arguments, i.e. may need to be finalised. This
1175                    // finalisation can only happen if we are actually using the default
1176                    // value—otherwise OpFinalise will crash on an already finalised (or
1177                    // non-thunk) value.
1178                    //
1179                    // Thus we use an additional local to track whether we wound up
1180                    // defaulting or not. `FinaliseRequest(false)` indicates that we should
1181                    // not finalise, as we did not default.
1182                    //
1183                    // We are being wasteful with VM stack space in case of default
1184                    // expressions that don't end up needing to be finalised. Unfortunately
1185                    // we only know better after compiling the default expression, so
1186                    // avoiding unnecessary locals would mean we'd need to modify the chunk
1187                    // after the fact.
1188                    self.push_op(Op::AttrsTrySelect, &pattern_entry.ident().unwrap());
1189                    let jump_to_default = self.push_op(Op::JumpIfNotFound, default_expr);
1190                    self.push_u16(0);
1191
1192                    self.emit_constant(Value::FinaliseRequest(false), default_expr);
1193
1194                    let jump_over_default = self.push_op(Op::Jump, default_expr);
1195                    self.push_u16(0);
1196
1197                    self.patch_jump(jump_to_default);
1198
1199                    // Does not need to thunked since compile() already does so when necessary
1200                    self.compile(idx, default_expr.clone());
1201
1202                    self.emit_constant(Value::FinaliseRequest(true), default_expr);
1203
1204                    self.patch_jump(jump_over_default);
1205                }
1206                TrackedFormal::NoDefault { pattern_entry, .. } => {
1207                    self.push_op(Op::AttrsSelect, &pattern_entry.ident().unwrap());
1208                }
1209            }
1210
1211            self.scope_mut().mark_initialised(idx);
1212            if let TrackedFormal::WithDefault {
1213                finalise_request_idx,
1214                ..
1215            } = tracked_formal
1216            {
1217                self.scope_mut().mark_initialised(*finalise_request_idx);
1218            }
1219        }
1220
1221        for tracked_formal in entries.iter() {
1222            if self.scope()[tracked_formal.local_idx()].needs_finaliser {
1223                let stack_idx = self.scope().stack_index(tracked_formal.local_idx());
1224                match tracked_formal {
1225                    TrackedFormal::NoDefault { .. } => panic!(
1226                        "Snix bug: local for pattern formal needs finaliser, but has no default expr"
1227                    ),
1228                    TrackedFormal::WithDefault {
1229                        finalise_request_idx,
1230                        ..
1231                    } => {
1232                        let finalise_request_stack_idx =
1233                            self.scope().stack_index(*finalise_request_idx);
1234
1235                        // TODO(sterni): better spans
1236                        self.push_op(Op::GetLocal, pattern);
1237                        self.push_uvarint(finalise_request_stack_idx.0 as u64);
1238                        let jump_over_finalise = self.push_op(Op::JumpIfNoFinaliseRequest, pattern);
1239                        self.push_u16(0);
1240                        self.push_op(Op::Finalise, pattern);
1241                        self.push_uvarint(stack_idx.0 as u64);
1242                        self.patch_jump(jump_over_finalise);
1243                        // Get rid of finaliser request value on the stack
1244                        self.push_op(Op::Pop, pattern);
1245                    }
1246                }
1247            }
1248        }
1249
1250        (
1251            (Formals {
1252                arguments,
1253                ellipsis,
1254                span,
1255                name: pat_bind_name,
1256            }),
1257            throw_idx,
1258        )
1259    }
1260
1261    fn compile_lambda(&mut self, slot: LocalIdx, node: &ast::Lambda) -> Option<CodeIdx> {
1262        // Compile the function itself, recording its formal arguments (if any)
1263        // for later use
1264        let formals = match node.param().unwrap() {
1265            ast::Param::Pattern(pat) => Some(self.compile_param_pattern(&pat)),
1266
1267            ast::Param::IdentParam(param) => {
1268                let name = param
1269                    .ident()
1270                    .unwrap()
1271                    .ident_token()
1272                    .unwrap()
1273                    .text()
1274                    .to_string();
1275
1276                let idx = self.declare_local(&param, &name);
1277                self.scope_mut().mark_initialised(idx);
1278
1279                // Store the parameter name for toXML
1280                self.context_mut().lambda.param_name = name;
1281
1282                None
1283            }
1284        };
1285
1286        self.compile(slot, node.body().unwrap());
1287        if let Some((formals, throw_idx)) = formals {
1288            self.context_mut().lambda.formals = Some(formals);
1289            // For formals, there's no single parameter name, use empty string
1290            self.context_mut().lambda.param_name = String::new();
1291            Some(throw_idx)
1292        } else {
1293            self.context_mut().lambda.formals = None;
1294            None
1295        }
1296    }
1297
1298    fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
1299    where
1300        N: ToSpan,
1301        F: FnOnce(&mut Compiler, LocalIdx),
1302    {
1303        self.compile_lambda_or_thunk(true, outer_slot, node, |comp, idx| {
1304            content(comp, idx);
1305            None
1306        })
1307    }
1308
1309    /// Compile an expression into a runtime closure or thunk
1310    fn compile_lambda_or_thunk<N, F>(
1311        &mut self,
1312        is_suspended_thunk: bool,
1313        outer_slot: LocalIdx,
1314        node: &N,
1315        content: F,
1316    ) where
1317        N: ToSpan,
1318        F: FnOnce(&mut Compiler, LocalIdx) -> Option<CodeIdx>,
1319    {
1320        let name = self.scope()[outer_slot].name();
1321        self.new_context();
1322
1323        // Set the (optional) name of the current slot on the lambda that is
1324        // being compiled.
1325        self.context_mut().lambda.name = name;
1326
1327        let span = self.span_for(node);
1328        let slot = self.scope_mut().declare_phantom(span, false);
1329        self.scope_mut().begin_scope();
1330
1331        let throw_idx = content(self, slot);
1332        self.cleanup_scope(node);
1333        if let Some(throw_idx) = throw_idx {
1334            self.patch_jump(throw_idx);
1335        }
1336
1337        // Pop the lambda context back off, and emit the finished
1338        // lambda as a constant.
1339        let mut compiled = self.contexts.pop().unwrap();
1340
1341        // Emit an instruction to inform the VM that the chunk has ended.
1342        compiled
1343            .lambda
1344            .chunk
1345            .push_op(Op::Return, self.span_for(node));
1346
1347        let lambda = Rc::new(compiled.lambda);
1348        if is_suspended_thunk {
1349            self.observer.observe_compiled_thunk(&lambda);
1350        } else {
1351            self.observer.observe_compiled_lambda(&lambda);
1352        }
1353
1354        // If no upvalues are captured, emit directly and move on.
1355        if lambda.upvalue_count == 0 && !compiled.captures_with_stack {
1356            self.emit_constant(
1357                if is_suspended_thunk {
1358                    Value::Thunk(Thunk::new_suspended(lambda, span))
1359                } else {
1360                    Value::Closure(Rc::new(Closure::new(lambda)))
1361                },
1362                node,
1363            );
1364            return;
1365        }
1366
1367        // Otherwise, we need to emit the variable number of
1368        // operands that allow the runtime to close over the
1369        // upvalues and leave a blueprint in the constant index from
1370        // which the result can be constructed.
1371        let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
1372
1373        let code_idx = self.push_op(
1374            if is_suspended_thunk {
1375                Op::ThunkSuspended
1376            } else {
1377                Op::ThunkClosure
1378            },
1379            node,
1380        );
1381        self.push_uvarint(blueprint_idx.0 as u64);
1382
1383        self.emit_upvalue_data(
1384            outer_slot,
1385            node,
1386            compiled.scope.upvalues,
1387            compiled.captures_with_stack,
1388        );
1389
1390        if !is_suspended_thunk && !self.scope()[outer_slot].needs_finaliser {
1391            if !self.scope()[outer_slot].must_thunk {
1392                // The closure has upvalues, but is not recursive. Therefore no
1393                // thunk is required, which saves us the overhead of
1394                // Rc<RefCell<>>
1395                self.chunk().code[code_idx.0] = Op::Closure as u8;
1396            } else {
1397                // This case occurs when a closure has upvalue-references to
1398                // itself but does not need a finaliser. Since no OpFinalise
1399                // will be emitted later on we synthesize one here. It is needed
1400                // here only to set [`Closure::is_finalised`] which is used for
1401                // sanity checks.
1402                #[cfg(debug_assertions)]
1403                {
1404                    self.push_op(Op::Finalise, &self.span_for(node));
1405                    self.push_uvarint(self.scope().stack_index(outer_slot).0 as u64);
1406                }
1407            }
1408        }
1409    }
1410
1411    fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
1412        // To call a function, we leave its arguments on the stack,
1413        // followed by the function expression itself, and then emit a
1414        // call instruction. This way, the stack is perfectly laid out
1415        // to enter the function call straight away.
1416        self.compile(slot, node.argument().unwrap());
1417        self.compile(slot, node.lambda().unwrap());
1418        self.emit_force(&node.lambda().unwrap());
1419        self.push_op(Op::Call, node);
1420    }
1421
1422    /// Emit the data instructions that the runtime needs to correctly
1423    /// assemble the upvalues struct.
1424    fn emit_upvalue_data<T: ToSpan>(
1425        &mut self,
1426        slot: LocalIdx,
1427        _: &T, // TODO
1428        upvalues: Vec<Upvalue>,
1429        capture_with: bool,
1430    ) {
1431        // Push the count of arguments to be expected, with one bit set to
1432        // indicate whether the with stack needs to be captured.
1433        let data = UpvalueData::new(upvalues.len(), capture_with);
1434        self.push_uvarint(data.into_raw());
1435
1436        for upvalue in upvalues {
1437            match upvalue.kind {
1438                UpvalueKind::Local(idx) => {
1439                    let target = &self.scope()[idx];
1440                    let stack_idx = self.scope().stack_index(idx);
1441
1442                    // If the target is not yet initialised, we need to defer
1443                    // the local access
1444                    if !target.initialised {
1445                        self.push_uvarint(Position::deferred_local(stack_idx).0);
1446                        self.scope_mut().mark_needs_finaliser(slot);
1447                    } else {
1448                        // a self-reference
1449                        if slot == idx {
1450                            self.scope_mut().mark_must_thunk(slot);
1451                        }
1452                        self.push_uvarint(Position::stack_index(stack_idx).0);
1453                    }
1454                }
1455
1456                UpvalueKind::Upvalue(idx) => {
1457                    self.push_uvarint(Position::upvalue_index(idx).0);
1458                }
1459            };
1460        }
1461    }
1462
1463    pub fn compile_cur_pos(&mut self, node: &ast::CurPos) {
1464        let value = match self.file.name() {
1465            crate::REPL_LOCATION => Value::Null,
1466            _ => {
1467                let span = self.span_for(node);
1468                let pos = self.file.find_line_col(span.low());
1469                let abs_path = std::fs::canonicalize(self.file.name())
1470                    .unwrap()
1471                    .to_string_lossy()
1472                    .to_string();
1473                let attrs = NixAttrs::from_iter([
1474                    ("line", Value::Integer((pos.line + 1) as i64)),
1475                    ("column", Value::Integer((pos.column + 1) as i64)),
1476                    ("file", Value::String(NixString::from(abs_path))),
1477                ]);
1478                Value::Attrs(attrs)
1479            }
1480        };
1481
1482        self.emit_constant(value, node);
1483    }
1484
1485    /// Emit the literal string value of an identifier. Required for
1486    /// several operations related to attribute sets, where
1487    /// identifiers are used as string keys.
1488    fn emit_literal_ident(&mut self, ident: &ast::Ident) {
1489        self.emit_constant(Value::String(ident.clone().into()), ident);
1490    }
1491
1492    /// Patch the jump instruction at the given index, setting its
1493    /// jump offset from the placeholder to the current code position.
1494    ///
1495    /// This is required because the actual target offset of jumps is
1496    /// not known at the time when the jump operation itself is
1497    /// emitted.
1498    fn patch_jump(&mut self, idx: CodeIdx) {
1499        self.chunk().patch_jump(idx.0);
1500    }
1501
1502    /// Decrease scope depth of the current function and emit
1503    /// instructions to clean up the stack at runtime.
1504    fn cleanup_scope<N: ToSpan>(&mut self, node: &N) {
1505        // When ending a scope, all corresponding locals need to be
1506        // removed, but the value of the body needs to remain on the
1507        // stack. This is implemented by a separate instruction.
1508        let (popcount, unused_spans) = self.scope_mut().end_scope();
1509
1510        for span in &unused_spans {
1511            self.emit_warning(span, WarningKind::UnusedBinding);
1512        }
1513
1514        if popcount > 0 {
1515            self.push_op(Op::CloseScope, node);
1516            self.push_uvarint(popcount as u64);
1517        }
1518    }
1519
1520    /// Open a new lambda context within which to compile a function,
1521    /// closure or thunk.
1522    fn new_context(&mut self) {
1523        self.contexts.push(self.context().inherit());
1524    }
1525
1526    /// Declare a local variable known in the scope that is being
1527    /// compiled by pushing it to the locals. This is used to
1528    /// determine the stack offset of variables.
1529    fn declare_local<S: Into<String>, N: ToSpan>(&mut self, node: &N, name: S) -> LocalIdx {
1530        let name = name.into();
1531        let depth = self.scope().scope_depth();
1532
1533        // Do this little dance to turn name:&'a str into the same
1534        // string with &'static lifetime, as required by WarningKind
1535        if let Some((global_ident, _)) = self.globals.get_key_value(name.as_str()) {
1536            self.emit_warning(node, WarningKind::ShadowedGlobal(global_ident));
1537        }
1538
1539        let span = self.span_for(node);
1540        let (idx, shadowed) = self.scope_mut().declare_local(name, span);
1541
1542        if let Some(shadow_idx) = shadowed {
1543            let other = &self.scope()[shadow_idx];
1544            if other.depth == depth {
1545                self.emit_error(node, ErrorKind::VariableAlreadyDefined(other.span));
1546            }
1547        }
1548
1549        idx
1550    }
1551
1552    /// Determine whether the current lambda context has any ancestors
1553    /// that use dynamic scope resolution, and mark contexts as
1554    /// needing to capture their enclosing `with`-stack in their
1555    /// upvalues.
1556    fn has_dynamic_ancestor(&mut self) -> bool {
1557        let mut ancestor_has_with = false;
1558
1559        for ctx in self.contexts.iter_mut() {
1560            if ancestor_has_with {
1561                // If the ancestor has an active with stack, mark this
1562                // lambda context as needing to capture it.
1563                ctx.captures_with_stack = true;
1564            } else {
1565                // otherwise, check this context and move on
1566                ancestor_has_with = ctx.scope.has_with();
1567            }
1568        }
1569
1570        ancestor_has_with
1571    }
1572
1573    fn emit_force<N: ToSpan>(&mut self, node: &N) {
1574        self.push_op(Op::Force, node);
1575    }
1576
1577    fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind) {
1578        let span = self.span_for(node);
1579        self.warnings.push(EvalWarning { kind, span })
1580    }
1581
1582    fn emit_error<N: ToSpan>(&mut self, node: &N, kind: ErrorKind) {
1583        let span = self.span_for(node);
1584        self.errors
1585            .push(Error::new(kind, span, self.source.clone()))
1586    }
1587}
1588
1589/// Convert a non-dynamic string expression to a string if possible.
1590fn expr_static_str(node: &ast::Str) -> Option<SmolStr> {
1591    let mut parts = node.normalized_parts();
1592
1593    if parts.len() != 1 {
1594        return None;
1595    }
1596
1597    if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
1598        return Some(SmolStr::new(lit));
1599    }
1600
1601    None
1602}
1603
1604/// Convert the provided `ast::Attr` into a statically known string if
1605/// possible.
1606fn expr_static_attr_str(node: &ast::Attr) -> Option<SmolStr> {
1607    match node {
1608        ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
1609        ast::Attr::Str(s) => expr_static_str(s),
1610
1611        // The dynamic node type is just a wrapper. C++ Nix does not care
1612        // about the dynamic wrapper when determining whether the node
1613        // itself is dynamic, it depends solely on the expression inside
1614        // (i.e. `let ${"a"} = 1; in a` is valid).
1615        ast::Attr::Dynamic(dynamic) => match dynamic.expr().unwrap() {
1616            ast::Expr::Str(s) => expr_static_str(&s),
1617            _ => None,
1618        },
1619    }
1620}
1621
1622/// Check whether a path contains any interpolated expressions.
1623fn is_interpolated_path(parts: &[InterpolPart<PathContent>]) -> bool {
1624    parts
1625        .iter()
1626        .any(|part| matches!(part, ast::InterpolPart::Interpolation(_)))
1627}
1628
1629/// Create a delayed source-only builtin compilation, for a builtin
1630/// which is written in Nix code.
1631///
1632/// **Important:** snix *panics* if a builtin with invalid source code
1633/// is supplied. This is because there is no user-friendly way to
1634/// thread the errors out of this function right now.
1635fn compile_src_builtin(
1636    name: &'static str,
1637    code: &str,
1638    source: SourceCode,
1639    weak: &Weak<GlobalsMap>,
1640) -> Value {
1641    use std::fmt::Write;
1642
1643    let parsed = rnix::ast::Root::parse(code);
1644
1645    if !parsed.errors().is_empty() {
1646        let mut out = format!("BUG: code for source-builtin '{name}' had parser errors");
1647        for error in parsed.errors() {
1648            writeln!(out, "{error}").unwrap();
1649        }
1650
1651        panic!("{}", out);
1652    }
1653
1654    let file = source.add_file(format!("<src-builtins/{name}.nix>"), code.to_string());
1655    let weak = weak.clone();
1656
1657    Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1658        let result = compile(
1659            &parsed.tree().expr().unwrap(),
1660            None,
1661            weak.upgrade().unwrap(),
1662            None,
1663            &source,
1664            &file,
1665            Default::default(),
1666        )
1667        .map_err(|e| ErrorKind::NativeError {
1668            gen_type: "derivation",
1669            err: Box::new(e),
1670        })?;
1671
1672        if !result.errors.is_empty() {
1673            return Err(ErrorKind::ImportCompilerError {
1674                path: format!("src-builtins/{name}.nix").into(),
1675                errors: result.errors,
1676            });
1677        }
1678
1679        Ok(Value::Thunk(Thunk::new_suspended(result.lambda, file.span)))
1680    })))
1681}
1682
1683/// Prepare the full set of globals available in evaluated code. These
1684/// are constructed from the set of builtins supplied by the caller,
1685/// which are made available globally under the `builtins` identifier.
1686///
1687/// A subset of builtins (specified by [`GLOBAL_BUILTINS`]) is
1688/// available globally *iff* they are set.
1689///
1690/// Optionally adds the `import` feature if desired by the caller.
1691pub fn prepare_globals(
1692    builtins: Vec<(&'static str, Value)>,
1693    src_builtins: Vec<(&'static str, &'static str)>,
1694    source: SourceCode,
1695    enable_import: bool,
1696) -> Rc<GlobalsMap> {
1697    Rc::new_cyclic(Box::new(move |weak: &Weak<GlobalsMap>| {
1698        // First step is to construct the builtins themselves as
1699        // `NixAttrs`.
1700        let mut builtins: GlobalsMap = FxHashMap::from_iter(builtins);
1701
1702        // At this point, optionally insert `import` if enabled. To
1703        // "tie the knot" of `import` needing the full set of globals
1704        // to instantiate its compiler, the `Weak` reference is passed
1705        // here.
1706        if enable_import {
1707            let import = Value::Builtin(import::builtins_import(weak, source.clone()));
1708            builtins.insert("import", import);
1709        }
1710
1711        // Next, the actual map of globals which the compiler will use
1712        // to resolve identifiers is constructed.
1713        let mut globals: GlobalsMap = FxHashMap::default();
1714
1715        // builtins contain themselves (`builtins.builtins`), which we
1716        // can resolve by manually constructing a suspended thunk that
1717        // dereferences the same weak pointer as above.
1718        let weak_globals = weak.clone();
1719        builtins.insert(
1720            "builtins",
1721            Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1722                Ok(weak_globals
1723                    .upgrade()
1724                    .unwrap()
1725                    .get("builtins")
1726                    .cloned()
1727                    .unwrap())
1728            }))),
1729        );
1730
1731        // Insert top-level static value builtins.
1732        globals.insert("true", Value::Bool(true));
1733        globals.insert("false", Value::Bool(false));
1734        globals.insert("null", Value::Null);
1735
1736        // If "source builtins" were supplied, compile them and insert
1737        // them.
1738        builtins.extend(src_builtins.into_iter().map(move |(name, code)| {
1739            let compiled = compile_src_builtin(name, code, source.clone(), weak);
1740            (name, compiled)
1741        }));
1742
1743        // Construct the actual `builtins` attribute set and insert it
1744        // in the global scope.
1745        globals.insert(
1746            "builtins",
1747            Value::attrs(NixAttrs::from_iter(builtins.clone())),
1748        );
1749
1750        // Finally, the builtins that should be globally available are
1751        // "elevated" to the outer scope.
1752        for global in GLOBAL_BUILTINS {
1753            if let Some(builtin) = builtins.get(global).cloned() {
1754                globals.insert(global, builtin);
1755            }
1756        }
1757
1758        globals
1759    }))
1760}
1761
1762pub fn compile(
1763    expr: &ast::Expr,
1764    location: Option<PathBuf>,
1765    globals: Rc<GlobalsMap>,
1766    env: Option<&FxHashMap<SmolStr, Value>>,
1767    source: &SourceCode,
1768    file: &codemap::File,
1769    observer: OptionalCompilerObserver<'_>,
1770) -> EvalResult<CompilationOutput> {
1771    let mut c = Compiler::new(location, globals.clone(), env, source, file, observer)?;
1772
1773    let root_span = c.span_for(expr);
1774    let root_slot = c.scope_mut().declare_phantom(root_span, false);
1775    c.compile(root_slot, expr.clone());
1776
1777    // The final operation of any top-level Nix program must always be
1778    // `OpForce`. A thunk should not be returned to the user in an
1779    // unevaluated state (though in practice, a value *containing* a
1780    // thunk might be returned).
1781    c.emit_force(expr);
1782    if let Some(env) = env
1783        && !env.is_empty()
1784    {
1785        c.push_op(Op::CloseScope, &root_span);
1786        c.push_uvarint(env.len() as u64);
1787    }
1788    c.push_op(Op::Return, &root_span);
1789
1790    let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
1791    c.observer.observe_compiled_toplevel(&lambda);
1792
1793    Ok(CompilationOutput {
1794        lambda,
1795        warnings: c.warnings,
1796        errors: c.errors,
1797    })
1798}