Skip to main content

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