1mod bindings;
17mod import;
18mod optimiser;
19mod scope;
20
21use codemap::Span;
22use rnix::ast::{self, AstToken};
23use rustc_hash::FxHashMap;
24use smol_str::SmolStr;
25use std::collections::BTreeMap;
26use std::path::{Path, PathBuf};
27use std::rc::{Rc, Weak};
28
29use crate::CoercionKind;
30use crate::SourceCode;
31use crate::chunk::Chunk;
32use crate::errors::{CatchableErrorKind, Error, ErrorKind, EvalResult};
33use crate::observer::CompilerObserver;
34use crate::opcode::{CodeIdx, Op, Position, UpvalueIdx};
35use crate::spans::ToSpan;
36use crate::value::{Closure, Formals, Lambda, NixAttrs, Thunk, Value};
37use crate::warnings::{EvalWarning, WarningKind};
38
39use self::scope::{LocalIdx, LocalPosition, Scope, Upvalue, UpvalueKind};
40
41pub struct CompilationOutput {
45 pub lambda: Rc<Lambda>,
46 pub warnings: Vec<EvalWarning>,
47 pub errors: Vec<Error>,
48}
49
50struct LambdaCtx {
52 lambda: Lambda,
53 scope: Scope,
54 captures_with_stack: bool,
55}
56
57impl LambdaCtx {
58 fn new() -> Self {
59 LambdaCtx {
60 lambda: Lambda::default(),
61 scope: Default::default(),
62 captures_with_stack: false,
63 }
64 }
65
66 fn inherit(&self) -> Self {
67 LambdaCtx {
68 lambda: Lambda::default(),
69 scope: self.scope.inherit(),
70 captures_with_stack: false,
71 }
72 }
73}
74
75enum TrackedFormal {
87 NoDefault {
88 local_idx: LocalIdx,
89 pattern_entry: ast::PatEntry,
90 },
91 WithDefault {
92 local_idx: LocalIdx,
93 finalise_request_idx: LocalIdx,
96 default_expr: ast::Expr,
97 pattern_entry: ast::PatEntry,
98 },
99}
100
101impl TrackedFormal {
102 fn pattern_entry(&self) -> &ast::PatEntry {
103 match self {
104 TrackedFormal::NoDefault { pattern_entry, .. } => pattern_entry,
105 TrackedFormal::WithDefault { pattern_entry, .. } => pattern_entry,
106 }
107 }
108 fn local_idx(&self) -> LocalIdx {
109 match self {
110 TrackedFormal::NoDefault { local_idx, .. } => *local_idx,
111 TrackedFormal::WithDefault { local_idx, .. } => *local_idx,
112 }
113 }
114}
115
116pub type GlobalsMap = FxHashMap<&'static str, Value>;
119
120const GLOBAL_BUILTINS: &[&str] = &[
125 "abort",
126 "baseNameOf",
127 "derivation",
128 "derivationStrict",
129 "dirOf",
130 "fetchGit",
131 "fetchMercurial",
132 "fetchTarball",
133 "fromTOML",
134 "import",
135 "isNull",
136 "map",
137 "placeholder",
138 "removeAttrs",
139 "scopedImport",
140 "throw",
141 "toString",
142 "__curPos",
143];
144
145pub struct Compiler<'source, 'observer> {
146 contexts: Vec<LambdaCtx>,
147 warnings: Vec<EvalWarning>,
148 errors: Vec<Error>,
149 root_dir: PathBuf,
150
151 globals: Rc<GlobalsMap>,
158
159 source: &'source SourceCode,
162
163 file: &'source codemap::File,
166
167 observer: &'observer mut dyn CompilerObserver,
170
171 dead_scope: usize,
175}
176
177impl Compiler<'_, '_> {
178 pub(super) fn span_for<S: ToSpan>(&self, to_span: &S) -> Span {
179 to_span.span_for(self.file)
180 }
181}
182
183impl<'source, 'observer> Compiler<'source, 'observer> {
185 pub(crate) fn new(
186 location: Option<PathBuf>,
187 globals: Rc<GlobalsMap>,
188 env: Option<&FxHashMap<SmolStr, Value>>,
189 source: &'source SourceCode,
190 file: &'source codemap::File,
191 observer: &'observer mut dyn CompilerObserver,
192 ) -> EvalResult<Self> {
193 let mut root_dir = match location {
194 Some(dir) if cfg!(target_arch = "wasm32") || dir.is_absolute() => Ok(dir),
195 _ => {
196 let current_dir = std::env::current_dir().map_err(|e| {
197 Error::new(
198 ErrorKind::RelativePathResolution(format!(
199 "could not determine current directory: {e}"
200 )),
201 file.span,
202 source.clone(),
203 )
204 })?;
205 if let Some(dir) = location {
206 Ok(current_dir.join(dir))
207 } else {
208 Ok(current_dir)
209 }
210 }
211 }?;
212
213 if root_dir.is_file() {
217 root_dir.pop();
218 }
219
220 #[cfg(not(target_arch = "wasm32"))]
221 debug_assert!(root_dir.is_absolute());
222
223 let mut compiler = Self {
224 root_dir,
225 source,
226 file,
227 observer,
228 globals,
229 contexts: vec![LambdaCtx::new()],
230 warnings: vec![],
231 errors: vec![],
232 dead_scope: 0,
233 };
234
235 if let Some(env) = env {
236 compiler.compile_env(env);
237 }
238
239 Ok(compiler)
240 }
241}
242
243impl Compiler<'_, '_> {
246 fn context(&self) -> &LambdaCtx {
247 &self.contexts[self.contexts.len() - 1]
248 }
249
250 fn context_mut(&mut self) -> &mut LambdaCtx {
251 let idx = self.contexts.len() - 1;
252 &mut self.contexts[idx]
253 }
254
255 fn chunk(&mut self) -> &mut Chunk {
256 &mut self.context_mut().lambda.chunk
257 }
258
259 fn scope(&self) -> &Scope {
260 &self.context().scope
261 }
262
263 fn scope_mut(&mut self) -> &mut Scope {
264 &mut self.context_mut().scope
265 }
266
267 fn push_op<T: ToSpan>(&mut self, data: Op, node: &T) -> CodeIdx {
270 if self.dead_scope > 0 {
271 return CodeIdx(0);
272 }
273
274 let span = self.span_for(node);
275 CodeIdx(self.chunk().push_op(data, span))
276 }
277
278 fn push_u8(&mut self, data: u8) {
279 if self.dead_scope > 0 {
280 return;
281 }
282
283 self.chunk().code.push(data);
284 }
285
286 fn push_uvarint(&mut self, data: u64) {
287 if self.dead_scope > 0 {
288 return;
289 }
290
291 self.chunk().push_uvarint(data);
292 }
293
294 fn push_u16(&mut self, data: u16) {
295 if self.dead_scope > 0 {
296 return;
297 }
298
299 self.chunk().push_u16(data);
300 }
301
302 pub(super) fn emit_constant<T: ToSpan>(&mut self, value: Value, node: &T) {
305 if self.dead_scope > 0 {
306 return;
307 }
308
309 let idx = self.chunk().push_constant(value);
310 self.push_op(Op::Constant, node);
311 self.push_uvarint(idx.0 as u64);
312 }
313}
314
315impl Compiler<'_, '_> {
317 fn compile(&mut self, slot: LocalIdx, expr: ast::Expr) {
318 let expr = optimiser::optimise_expr(self, slot, expr);
319
320 match &expr {
321 ast::Expr::Literal(literal) => self.compile_literal(literal),
322 ast::Expr::Path(path) => self.compile_path(slot, path),
323 ast::Expr::Str(s) => self.compile_str(slot, s),
324
325 ast::Expr::UnaryOp(op) => self.thunk(slot, op, move |c, s| c.compile_unary_op(s, op)),
326
327 ast::Expr::BinOp(binop) => {
328 self.thunk(slot, binop, move |c, s| c.compile_binop(s, binop))
329 }
330
331 ast::Expr::HasAttr(has_attr) => {
332 self.thunk(slot, has_attr, move |c, s| c.compile_has_attr(s, has_attr))
333 }
334
335 ast::Expr::List(list) => self.thunk(slot, list, move |c, s| c.compile_list(s, list)),
336
337 ast::Expr::AttrSet(attrs) => {
338 self.thunk(slot, attrs, move |c, s| c.compile_attr_set(s, attrs))
339 }
340
341 ast::Expr::Select(select) => {
342 self.thunk(slot, select, move |c, s| c.compile_select(s, select))
343 }
344
345 ast::Expr::Assert(assert) => {
346 self.thunk(slot, assert, move |c, s| c.compile_assert(s, assert))
347 }
348 ast::Expr::IfElse(if_else) => {
349 self.thunk(slot, if_else, move |c, s| c.compile_if_else(s, if_else))
350 }
351
352 ast::Expr::LetIn(let_in) => {
353 self.thunk(slot, let_in, move |c, s| c.compile_let_in(s, let_in))
354 }
355
356 ast::Expr::Ident(ident) => self.compile_ident(slot, ident),
357 ast::Expr::With(with) => self.thunk(slot, with, |c, s| c.compile_with(s, with)),
358 ast::Expr::Lambda(lambda) => self.thunk(slot, lambda, move |c, s| {
359 c.compile_lambda_or_thunk(false, s, lambda, |c, s| c.compile_lambda(s, lambda))
360 }),
361 ast::Expr::Apply(apply) => {
362 self.thunk(slot, apply, move |c, s| c.compile_apply(s, apply))
363 }
364
365 ast::Expr::Paren(paren) => self.compile(slot, paren.expr().unwrap()),
368
369 ast::Expr::LegacyLet(legacy_let) => self.thunk(slot, legacy_let, move |c, s| {
370 c.compile_legacy_let(s, legacy_let)
371 }),
372
373 ast::Expr::Root(_) => unreachable!("there cannot be more than one root"),
374 ast::Expr::Error(_) => unreachable!("compile is only called on validated trees"),
375 }
376 }
377
378 fn compile_dead_code(&mut self, slot: LocalIdx, node: ast::Expr) {
385 self.dead_scope += 1;
386 self.compile(slot, node);
387 self.dead_scope -= 1;
388 }
389
390 fn compile_literal(&mut self, node: &ast::Literal) {
391 let value = match node.kind() {
392 ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()),
393 ast::LiteralKind::Integer(i) => match i.value() {
394 Ok(v) => Value::Integer(v),
395 Err(err) => return self.emit_error(node, err.into()),
396 },
397
398 ast::LiteralKind::Uri(u) => {
399 self.emit_warning(node, WarningKind::DeprecatedLiteralURL);
400 Value::from(u.syntax().text())
401 }
402 };
403
404 self.emit_constant(value, node);
405 }
406
407 fn compile_path(&mut self, slot: LocalIdx, node: &ast::Path) {
408 let raw_path = node.to_string();
412 let path = if raw_path.starts_with('/') {
413 Path::new(&raw_path).to_owned()
414 } else if raw_path.starts_with('~') {
415 debug_assert!(raw_path.len() > 2 && raw_path.starts_with("~/"));
418
419 let home_relative_path = &raw_path[2..(raw_path.len())];
420 self.emit_constant(
421 Value::UnresolvedPath(Box::new(home_relative_path.into())),
422 node,
423 );
424 self.push_op(Op::ResolveHomePath, node);
425 return;
426 } else if raw_path.starts_with('<') {
427 if raw_path.len() == 2 {
429 return self.emit_constant(
430 Value::Catchable(Box::new(CatchableErrorKind::NixPathResolution(
431 "Empty <> path not allowed".into(),
432 ))),
433 node,
434 );
435 }
436 let path = &raw_path[1..(raw_path.len() - 1)];
437 return self.thunk(slot, node, move |c, _| {
439 c.emit_constant(Value::UnresolvedPath(Box::new(path.into())), node);
440 c.push_op(Op::FindFile, node);
441 });
442 } else {
443 let mut buf = self.root_dir.clone();
444 buf.push(&raw_path);
445 buf
446 };
447
448 let value = Value::Path(Box::new(crate::value::canon_path(path)));
451 self.emit_constant(value, node);
452 }
453
454 fn compile_str_parts(
458 &mut self,
459 slot: LocalIdx,
460 parent_node: &ast::Str,
461 parts: Vec<ast::InterpolPart<String>>,
462 ) {
463 for part in parts.iter().rev() {
468 match part {
469 ast::InterpolPart::Interpolation(ipol) => {
474 self.compile(slot, ipol.expr().unwrap());
475 self.push_op(Op::CoerceToString, ipol);
477
478 let encoded: u8 = CoercionKind {
479 strong: false,
480 import_paths: true,
481 }
482 .into();
483
484 self.push_u8(encoded);
485 }
486
487 ast::InterpolPart::Literal(lit) => {
488 self.emit_constant(Value::from(lit.as_str()), parent_node);
489 }
490 }
491 }
492
493 if parts.len() != 1 {
494 self.push_op(Op::Interpolate, parent_node);
495 self.push_uvarint(parts.len() as u64);
496 }
497 }
498
499 fn compile_str(&mut self, slot: LocalIdx, node: &ast::Str) {
500 let parts = node.normalized_parts();
501
502 if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) {
508 self.thunk(slot, node, move |c, s| {
509 c.compile_str_parts(s, node, parts);
510 });
511 } else {
512 self.compile_str_parts(slot, node, parts);
513 }
514 }
515
516 fn compile_unary_op(&mut self, slot: LocalIdx, op: &ast::UnaryOp) {
517 self.compile(slot, op.expr().unwrap());
518 self.emit_force(op);
519
520 let opcode = match op.operator().unwrap() {
521 ast::UnaryOpKind::Invert => Op::Invert,
522 ast::UnaryOpKind::Negate => Op::Negate,
523 };
524
525 self.push_op(opcode, op);
526 }
527
528 fn compile_binop(&mut self, slot: LocalIdx, op: &ast::BinOp) {
529 use ast::BinOpKind;
530
531 match op.operator().unwrap() {
537 BinOpKind::And => return self.compile_and(slot, op),
538 BinOpKind::Or => return self.compile_or(slot, op),
539 BinOpKind::Implication => return self.compile_implication(slot, op),
540 _ => {}
541 };
542
543 self.compile(slot, op.lhs().unwrap());
547 self.emit_force(&op.lhs().unwrap());
548
549 self.compile(slot, op.rhs().unwrap());
550 self.emit_force(&op.rhs().unwrap());
551
552 match op.operator().unwrap() {
553 BinOpKind::Add => self.push_op(Op::Add, op),
554 BinOpKind::Sub => self.push_op(Op::Sub, op),
555 BinOpKind::Mul => self.push_op(Op::Mul, op),
556 BinOpKind::Div => self.push_op(Op::Div, op),
557 BinOpKind::Update => self.push_op(Op::AttrsUpdate, op),
558 BinOpKind::Equal => self.push_op(Op::Equal, op),
559 BinOpKind::Less => self.push_op(Op::Less, op),
560 BinOpKind::LessOrEq => self.push_op(Op::LessOrEq, op),
561 BinOpKind::More => self.push_op(Op::More, op),
562 BinOpKind::MoreOrEq => self.push_op(Op::MoreOrEq, op),
563 BinOpKind::Concat => self.push_op(Op::Concat, op),
564
565 BinOpKind::NotEqual => {
566 self.push_op(Op::Equal, op);
567 self.push_op(Op::Invert, op)
568 }
569
570 BinOpKind::And | BinOpKind::Implication | BinOpKind::Or => {
572 unreachable!()
573 }
574 };
575 }
576
577 fn compile_and(&mut self, slot: LocalIdx, node: &ast::BinOp) {
578 debug_assert!(
579 matches!(node.operator(), Some(ast::BinOpKind::And)),
580 "compile_and called with wrong operator kind: {:?}",
581 node.operator(),
582 );
583
584 self.compile(slot, node.lhs().unwrap());
586 self.emit_force(&node.lhs().unwrap());
587
588 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
589 self.push_u16(0);
590 let end_idx = self.push_op(Op::JumpIfFalse, node);
593 self.push_u16(0);
594
595 self.push_op(Op::Pop, node);
599 self.compile(slot, node.rhs().unwrap());
600 self.emit_force(&node.rhs().unwrap());
601
602 self.patch_jump(end_idx);
603 self.push_op(Op::AssertBool, node);
604 self.patch_jump(throw_idx);
605 }
606
607 fn compile_or(&mut self, slot: LocalIdx, node: &ast::BinOp) {
608 debug_assert!(
609 matches!(node.operator(), Some(ast::BinOpKind::Or)),
610 "compile_or called with wrong operator kind: {:?}",
611 node.operator(),
612 );
613
614 self.compile(slot, node.lhs().unwrap());
616 self.emit_force(&node.lhs().unwrap());
617
618 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
619 self.push_u16(0);
620 let end_idx = self.push_op(Op::JumpIfTrue, node);
623 self.push_u16(0);
624 self.push_op(Op::Pop, node);
625 self.compile(slot, node.rhs().unwrap());
626 self.emit_force(&node.rhs().unwrap());
627
628 self.patch_jump(end_idx);
629 self.push_op(Op::AssertBool, node);
630 self.patch_jump(throw_idx);
631 }
632
633 fn compile_implication(&mut self, slot: LocalIdx, node: &ast::BinOp) {
634 debug_assert!(
635 matches!(node.operator(), Some(ast::BinOpKind::Implication)),
636 "compile_implication called with wrong operator kind: {:?}",
637 node.operator(),
638 );
639
640 self.compile(slot, node.lhs().unwrap());
642 self.emit_force(&node.lhs().unwrap());
643 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
644 self.push_u16(0);
645 self.push_op(Op::Invert, node);
646
647 let end_idx = self.push_op(Op::JumpIfTrue, node);
649 self.push_u16(0);
650
651 self.push_op(Op::Pop, node);
652 self.compile(slot, node.rhs().unwrap());
653 self.emit_force(&node.rhs().unwrap());
654
655 self.patch_jump(end_idx);
656 self.push_op(Op::AssertBool, node);
657 self.patch_jump(throw_idx);
658 }
659
660 fn compile_list(&mut self, slot: LocalIdx, node: &ast::List) {
668 let mut count = 0;
669
670 self.scope_mut().begin_scope();
673
674 for item in node.items() {
675 let item_slot = match count {
679 0 => slot,
680 _ => {
681 let item_span = self.span_for(&item);
682 self.scope_mut().declare_phantom(item_span, false)
683 }
684 };
685
686 count += 1;
687 self.compile(item_slot, item);
688 self.scope_mut().mark_initialised(item_slot);
689 }
690
691 self.push_op(Op::List, node);
692 self.push_uvarint(count as u64);
693 self.scope_mut().end_scope();
694 }
695
696 fn compile_attr(&mut self, slot: LocalIdx, node: &ast::Attr) {
697 match node {
698 ast::Attr::Dynamic(dynamic) => {
699 self.compile(slot, dynamic.expr().unwrap());
700 self.emit_force(&dynamic.expr().unwrap());
701 }
702
703 ast::Attr::Str(s) => {
704 self.compile_str(slot, s);
705 self.emit_force(s);
706 }
707
708 ast::Attr::Ident(ident) => self.emit_literal_ident(ident),
709 }
710 }
711
712 fn compile_has_attr(&mut self, slot: LocalIdx, node: &ast::HasAttr) {
713 self.compile(slot, node.expr().unwrap());
715 self.emit_force(node);
716
717 for (count, fragment) in node.attrpath().unwrap().attrs().enumerate() {
720 if count > 0 {
721 self.push_op(Op::AttrsTrySelect, &fragment);
722 self.emit_force(&fragment);
723 }
724
725 self.compile_attr(slot, &fragment);
726 }
727
728 self.push_op(Op::HasAttr, node);
731 }
732
733 fn optimise_select(&mut self, path: &ast::Attrpath) -> bool {
744 if let Some((Op::Constant, op_idx)) = self.chunk().last_op() {
755 let (idx, _) = self.chunk().read_uvarint(op_idx + 1);
756 let constant = &mut self.chunk().constants[idx as usize];
757 if let Value::Attrs(attrs) = constant {
758 let mut path_iter = path.attrs();
759
760 if let (Some(attr), None) = (path_iter.next(), path_iter.next()) {
764 if let Some(ident) = expr_static_attr_str(&attr) {
766 if let Some(selected_value) = attrs.select(ident.as_bytes()) {
767 *constant = selected_value.clone();
768 return true;
769 }
770 }
771 }
772 }
773 }
774
775 false
776 }
777
778 fn compile_select(&mut self, slot: LocalIdx, node: &ast::Select) {
779 let set = node.expr().unwrap();
780 let path = node.attrpath().unwrap();
781
782 if node.or_token().is_some() {
783 return self.compile_select_or(slot, set, path, node.default_expr().unwrap());
784 }
785
786 self.compile(slot, set.clone());
788 if self.optimise_select(&path) {
789 return;
790 }
791
792 for fragment in path.attrs() {
797 self.emit_force(&set);
799
800 self.compile_attr(slot, &fragment);
801 self.push_op(Op::AttrsSelect, &fragment);
802 }
803 }
804
805 fn compile_select_or(
835 &mut self,
836 slot: LocalIdx,
837 set: ast::Expr,
838 path: ast::Attrpath,
839 default: ast::Expr,
840 ) {
841 self.compile(slot, set);
842 if self.optimise_select(&path) {
843 return;
844 }
845
846 let mut jumps = vec![];
847
848 for fragment in path.attrs() {
849 self.emit_force(&fragment);
850 self.compile_attr(slot, &fragment.clone());
851 self.push_op(Op::AttrsTrySelect, &fragment);
852 jumps.push(self.push_op(Op::JumpIfNotFound, &fragment));
853 self.push_u16(0);
854 }
855
856 let final_jump = self.push_op(Op::Jump, &path);
857 self.push_u16(0);
858
859 for jump in jumps {
860 self.patch_jump(jump);
861 }
862
863 self.compile(slot, default);
866 self.patch_jump(final_jump);
867 }
868
869 fn compile_assert(&mut self, slot: LocalIdx, node: &ast::Assert) {
882 self.compile(slot, node.condition().unwrap());
884 self.emit_force(&node.condition().unwrap());
885
886 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
887 self.push_u16(0);
888
889 let then_idx = self.push_op(Op::JumpIfFalse, node);
890 self.push_u16(0);
891
892 self.push_op(Op::Pop, node);
893 self.compile(slot, node.body().unwrap());
894
895 let else_idx = self.push_op(Op::Jump, node);
896 self.push_u16(0);
897
898 self.patch_jump(then_idx);
899 self.push_op(Op::Pop, node);
900 self.push_op(Op::AssertFail, &node.condition().unwrap());
901
902 self.patch_jump(else_idx);
903 self.patch_jump(throw_idx);
904 }
905
906 fn compile_if_else(&mut self, slot: LocalIdx, node: &ast::IfElse) {
920 self.compile(slot, node.condition().unwrap());
921 self.emit_force(&node.condition().unwrap());
922
923 let throw_idx = self.push_op(Op::JumpIfCatchable, &node.condition().unwrap());
924 self.push_u16(0);
925
926 let then_idx = self.push_op(Op::JumpIfFalse, &node.condition().unwrap());
927 self.push_u16(0);
928
929 self.push_op(Op::Pop, node); self.compile(slot, node.body().unwrap());
931
932 let else_idx = self.push_op(Op::Jump, node);
933 self.push_u16(0);
934
935 self.patch_jump(then_idx); self.push_op(Op::Pop, node); self.compile(slot, node.else_body().unwrap());
938
939 self.patch_jump(else_idx); self.patch_jump(throw_idx); }
942
943 fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
947 self.scope_mut().begin_scope();
948 self.compile(slot, node.namespace().unwrap());
952
953 let span = self.span_for(&node.namespace().unwrap());
954
955 let local_idx = self.scope_mut().declare_phantom(span, true);
961 let with_idx = self.scope().stack_index(local_idx);
962
963 self.scope_mut().push_with();
964
965 self.push_op(Op::PushWith, &node.namespace().unwrap());
966 self.push_uvarint(with_idx.0 as u64);
967
968 self.compile(slot, node.body().unwrap());
969
970 self.push_op(Op::PopWith, node);
971 self.scope_mut().pop_with();
972 self.cleanup_scope(node);
973 }
974
975 fn compile_param_pattern(&mut self, pattern: &ast::Pattern) -> (Formals, CodeIdx) {
1014 let span = self.span_for(pattern);
1015
1016 let (set_idx, pat_bind_name) = match pattern.pat_bind() {
1017 Some(name) => {
1018 let pat_bind_name = name.ident().unwrap().to_string();
1019 (
1020 self.declare_local(&name, pat_bind_name.clone()),
1021 Some(pat_bind_name),
1022 )
1023 }
1024 None => (self.scope_mut().declare_phantom(span, true), None),
1025 };
1026
1027 self.scope_mut().mark_initialised(set_idx);
1029 self.emit_force(pattern);
1030 let throw_idx = self.push_op(Op::JumpIfCatchable, pattern);
1031 self.push_u16(0);
1032
1033 self.push_op(Op::AssertAttrs, pattern);
1035
1036 let ellipsis = pattern.ellipsis_token().is_some();
1037 if !ellipsis {
1038 self.push_op(Op::ValidateClosedFormals, pattern);
1039 }
1040
1041 let mut entries: Vec<TrackedFormal> = vec![];
1045 let mut arguments = BTreeMap::default();
1046
1047 for entry in pattern.pat_entries() {
1048 let ident = entry.ident().unwrap();
1049 let idx = self.declare_local(&ident, ident.to_string());
1050
1051 arguments.insert(ident.into(), entry.default().is_some());
1052
1053 if let Some(default_expr) = entry.default() {
1054 entries.push(TrackedFormal::WithDefault {
1055 local_idx: idx,
1056 finalise_request_idx: {
1060 let span = self.span_for(&default_expr);
1061 self.scope_mut().declare_phantom(span, false)
1062 },
1063 default_expr,
1064 pattern_entry: entry,
1065 });
1066 } else {
1067 entries.push(TrackedFormal::NoDefault {
1068 local_idx: idx,
1069 pattern_entry: entry,
1070 });
1071 }
1072 }
1073
1074 let stack_idx = self.scope().stack_index(set_idx);
1077 for tracked_formal in entries.iter() {
1078 self.push_op(Op::GetLocal, pattern);
1079 self.push_uvarint(stack_idx.0 as u64);
1080 self.emit_literal_ident(&tracked_formal.pattern_entry().ident().unwrap());
1081
1082 let idx = tracked_formal.local_idx();
1083
1084 match tracked_formal {
1087 TrackedFormal::WithDefault {
1088 default_expr,
1089 pattern_entry,
1090 ..
1091 } => {
1092 self.push_op(Op::AttrsTrySelect, &pattern_entry.ident().unwrap());
1109 let jump_to_default = self.push_op(Op::JumpIfNotFound, default_expr);
1110 self.push_u16(0);
1111
1112 self.emit_constant(Value::FinaliseRequest(false), default_expr);
1113
1114 let jump_over_default = self.push_op(Op::Jump, default_expr);
1115 self.push_u16(0);
1116
1117 self.patch_jump(jump_to_default);
1118
1119 self.compile(idx, default_expr.clone());
1121
1122 self.emit_constant(Value::FinaliseRequest(true), default_expr);
1123
1124 self.patch_jump(jump_over_default);
1125 }
1126 TrackedFormal::NoDefault { pattern_entry, .. } => {
1127 self.push_op(Op::AttrsSelect, &pattern_entry.ident().unwrap());
1128 }
1129 }
1130
1131 self.scope_mut().mark_initialised(idx);
1132 if let TrackedFormal::WithDefault {
1133 finalise_request_idx,
1134 ..
1135 } = tracked_formal
1136 {
1137 self.scope_mut().mark_initialised(*finalise_request_idx);
1138 }
1139 }
1140
1141 for tracked_formal in entries.iter() {
1142 if self.scope()[tracked_formal.local_idx()].needs_finaliser {
1143 let stack_idx = self.scope().stack_index(tracked_formal.local_idx());
1144 match tracked_formal {
1145 TrackedFormal::NoDefault { .. } => panic!(
1146 "Snix bug: local for pattern formal needs finaliser, but has no default expr"
1147 ),
1148 TrackedFormal::WithDefault {
1149 finalise_request_idx,
1150 ..
1151 } => {
1152 let finalise_request_stack_idx =
1153 self.scope().stack_index(*finalise_request_idx);
1154
1155 self.push_op(Op::GetLocal, pattern);
1157 self.push_uvarint(finalise_request_stack_idx.0 as u64);
1158 let jump_over_finalise = self.push_op(Op::JumpIfNoFinaliseRequest, pattern);
1159 self.push_u16(0);
1160 self.push_op(Op::Finalise, pattern);
1161 self.push_uvarint(stack_idx.0 as u64);
1162 self.patch_jump(jump_over_finalise);
1163 self.push_op(Op::Pop, pattern);
1165 }
1166 }
1167 }
1168 }
1169
1170 (
1171 (Formals {
1172 arguments,
1173 ellipsis,
1174 span,
1175 name: pat_bind_name,
1176 }),
1177 throw_idx,
1178 )
1179 }
1180
1181 fn compile_lambda(&mut self, slot: LocalIdx, node: &ast::Lambda) -> Option<CodeIdx> {
1182 let formals = match node.param().unwrap() {
1185 ast::Param::Pattern(pat) => Some(self.compile_param_pattern(&pat)),
1186
1187 ast::Param::IdentParam(param) => {
1188 let name = param
1189 .ident()
1190 .unwrap()
1191 .ident_token()
1192 .unwrap()
1193 .text()
1194 .to_string();
1195
1196 let idx = self.declare_local(¶m, &name);
1197 self.scope_mut().mark_initialised(idx);
1198
1199 self.context_mut().lambda.param_name = name;
1201
1202 None
1203 }
1204 };
1205
1206 self.compile(slot, node.body().unwrap());
1207 if let Some((formals, throw_idx)) = formals {
1208 self.context_mut().lambda.formals = Some(formals);
1209 self.context_mut().lambda.param_name = String::new();
1211 Some(throw_idx)
1212 } else {
1213 self.context_mut().lambda.formals = None;
1214 None
1215 }
1216 }
1217
1218 fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
1219 where
1220 N: ToSpan,
1221 F: FnOnce(&mut Compiler, LocalIdx),
1222 {
1223 self.compile_lambda_or_thunk(true, outer_slot, node, |comp, idx| {
1224 content(comp, idx);
1225 None
1226 })
1227 }
1228
1229 fn compile_lambda_or_thunk<N, F>(
1231 &mut self,
1232 is_suspended_thunk: bool,
1233 outer_slot: LocalIdx,
1234 node: &N,
1235 content: F,
1236 ) where
1237 N: ToSpan,
1238 F: FnOnce(&mut Compiler, LocalIdx) -> Option<CodeIdx>,
1239 {
1240 let name = self.scope()[outer_slot].name();
1241 self.new_context();
1242
1243 self.context_mut().lambda.name = name;
1246
1247 let span = self.span_for(node);
1248 let slot = self.scope_mut().declare_phantom(span, false);
1249 self.scope_mut().begin_scope();
1250
1251 let throw_idx = content(self, slot);
1252 self.cleanup_scope(node);
1253 if let Some(throw_idx) = throw_idx {
1254 self.patch_jump(throw_idx);
1255 }
1256
1257 let mut compiled = self.contexts.pop().unwrap();
1260
1261 compiled
1263 .lambda
1264 .chunk
1265 .push_op(Op::Return, self.span_for(node));
1266
1267 let lambda = Rc::new(compiled.lambda);
1268 if is_suspended_thunk {
1269 self.observer.observe_compiled_thunk(&lambda);
1270 } else {
1271 self.observer.observe_compiled_lambda(&lambda);
1272 }
1273
1274 if lambda.upvalue_count == 0 && !compiled.captures_with_stack {
1276 self.emit_constant(
1277 if is_suspended_thunk {
1278 Value::Thunk(Thunk::new_suspended(lambda, span))
1279 } else {
1280 Value::Closure(Rc::new(Closure::new(lambda)))
1281 },
1282 node,
1283 );
1284 return;
1285 }
1286
1287 let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
1292
1293 let code_idx = self.push_op(
1294 if is_suspended_thunk {
1295 Op::ThunkSuspended
1296 } else {
1297 Op::ThunkClosure
1298 },
1299 node,
1300 );
1301 self.push_uvarint(blueprint_idx.0 as u64);
1302
1303 self.emit_upvalue_data(
1304 outer_slot,
1305 node,
1306 compiled.scope.upvalues,
1307 compiled.captures_with_stack,
1308 );
1309
1310 if !is_suspended_thunk && !self.scope()[outer_slot].needs_finaliser {
1311 if !self.scope()[outer_slot].must_thunk {
1312 self.chunk().code[code_idx.0] = Op::Closure as u8;
1316 } else {
1317 #[cfg(debug_assertions)]
1323 {
1324 self.push_op(Op::Finalise, &self.span_for(node));
1325 self.push_uvarint(self.scope().stack_index(outer_slot).0 as u64);
1326 }
1327 }
1328 }
1329 }
1330
1331 fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
1332 self.compile(slot, node.argument().unwrap());
1337 self.compile(slot, node.lambda().unwrap());
1338 self.emit_force(&node.lambda().unwrap());
1339 self.push_op(Op::Call, node);
1340 }
1341
1342 fn emit_upvalue_data<T: ToSpan>(
1345 &mut self,
1346 slot: LocalIdx,
1347 _: &T, upvalues: Vec<Upvalue>,
1349 capture_with: bool,
1350 ) {
1351 let mut count = (upvalues.len() as u64) << 1;
1354 if capture_with {
1355 count |= 1;
1356 }
1357 self.push_uvarint(count);
1358
1359 for upvalue in upvalues {
1360 match upvalue.kind {
1361 UpvalueKind::Local(idx) => {
1362 let target = &self.scope()[idx];
1363 let stack_idx = self.scope().stack_index(idx);
1364
1365 if !target.initialised {
1368 self.push_uvarint(Position::deferred_local(stack_idx).0);
1369 self.scope_mut().mark_needs_finaliser(slot);
1370 } else {
1371 if slot == idx {
1373 self.scope_mut().mark_must_thunk(slot);
1374 }
1375 self.push_uvarint(Position::stack_index(stack_idx).0);
1376 }
1377 }
1378
1379 UpvalueKind::Upvalue(idx) => {
1380 self.push_uvarint(Position::upvalue_index(idx).0);
1381 }
1382 };
1383 }
1384 }
1385
1386 fn emit_literal_ident(&mut self, ident: &ast::Ident) {
1390 self.emit_constant(Value::String(ident.clone().into()), ident);
1391 }
1392
1393 fn patch_jump(&mut self, idx: CodeIdx) {
1400 self.chunk().patch_jump(idx.0);
1401 }
1402
1403 fn cleanup_scope<N: ToSpan>(&mut self, node: &N) {
1406 let (popcount, unused_spans) = self.scope_mut().end_scope();
1410
1411 for span in &unused_spans {
1412 self.emit_warning(span, WarningKind::UnusedBinding);
1413 }
1414
1415 if popcount > 0 {
1416 self.push_op(Op::CloseScope, node);
1417 self.push_uvarint(popcount as u64);
1418 }
1419 }
1420
1421 fn new_context(&mut self) {
1424 self.contexts.push(self.context().inherit());
1425 }
1426
1427 fn declare_local<S: Into<String>, N: ToSpan>(&mut self, node: &N, name: S) -> LocalIdx {
1431 let name = name.into();
1432 let depth = self.scope().scope_depth();
1433
1434 if let Some((global_ident, _)) = self.globals.get_key_value(name.as_str()) {
1437 self.emit_warning(node, WarningKind::ShadowedGlobal(global_ident));
1438 }
1439
1440 let span = self.span_for(node);
1441 let (idx, shadowed) = self.scope_mut().declare_local(name, span);
1442
1443 if let Some(shadow_idx) = shadowed {
1444 let other = &self.scope()[shadow_idx];
1445 if other.depth == depth {
1446 self.emit_error(node, ErrorKind::VariableAlreadyDefined(other.span));
1447 }
1448 }
1449
1450 idx
1451 }
1452
1453 fn has_dynamic_ancestor(&mut self) -> bool {
1458 let mut ancestor_has_with = false;
1459
1460 for ctx in self.contexts.iter_mut() {
1461 if ancestor_has_with {
1462 ctx.captures_with_stack = true;
1465 } else {
1466 ancestor_has_with = ctx.scope.has_with();
1468 }
1469 }
1470
1471 ancestor_has_with
1472 }
1473
1474 fn emit_force<N: ToSpan>(&mut self, node: &N) {
1475 self.push_op(Op::Force, node);
1476 }
1477
1478 fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind) {
1479 let span = self.span_for(node);
1480 self.warnings.push(EvalWarning { kind, span })
1481 }
1482
1483 fn emit_error<N: ToSpan>(&mut self, node: &N, kind: ErrorKind) {
1484 let span = self.span_for(node);
1485 self.errors
1486 .push(Error::new(kind, span, self.source.clone()))
1487 }
1488}
1489
1490fn expr_static_str(node: &ast::Str) -> Option<SmolStr> {
1492 let mut parts = node.normalized_parts();
1493
1494 if parts.len() != 1 {
1495 return None;
1496 }
1497
1498 if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
1499 return Some(SmolStr::new(lit));
1500 }
1501
1502 None
1503}
1504
1505fn expr_static_attr_str(node: &ast::Attr) -> Option<SmolStr> {
1508 match node {
1509 ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
1510 ast::Attr::Str(s) => expr_static_str(s),
1511
1512 ast::Attr::Dynamic(dynamic) => match dynamic.expr().unwrap() {
1517 ast::Expr::Str(s) => expr_static_str(&s),
1518 _ => None,
1519 },
1520 }
1521}
1522
1523fn compile_src_builtin(
1530 name: &'static str,
1531 code: &str,
1532 source: SourceCode,
1533 weak: &Weak<GlobalsMap>,
1534) -> Value {
1535 use std::fmt::Write;
1536
1537 let parsed = rnix::ast::Root::parse(code);
1538
1539 if !parsed.errors().is_empty() {
1540 let mut out = format!("BUG: code for source-builtin '{name}' had parser errors");
1541 for error in parsed.errors() {
1542 writeln!(out, "{error}").unwrap();
1543 }
1544
1545 panic!("{}", out);
1546 }
1547
1548 let file = source.add_file(format!("<src-builtins/{name}.nix>"), code.to_string());
1549 let weak = weak.clone();
1550
1551 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1552 let result = compile(
1553 &parsed.tree().expr().unwrap(),
1554 None,
1555 weak.upgrade().unwrap(),
1556 None,
1557 &source,
1558 &file,
1559 &mut crate::observer::NoOpObserver {},
1560 )
1561 .map_err(|e| ErrorKind::NativeError {
1562 gen_type: "derivation",
1563 err: Box::new(e),
1564 })?;
1565
1566 if !result.errors.is_empty() {
1567 return Err(ErrorKind::ImportCompilerError {
1568 path: format!("src-builtins/{name}.nix").into(),
1569 errors: result.errors,
1570 });
1571 }
1572
1573 Ok(Value::Thunk(Thunk::new_suspended(result.lambda, file.span)))
1574 })))
1575}
1576
1577pub fn prepare_globals(
1586 builtins: Vec<(&'static str, Value)>,
1587 src_builtins: Vec<(&'static str, &'static str)>,
1588 source: SourceCode,
1589 enable_import: bool,
1590) -> Rc<GlobalsMap> {
1591 Rc::new_cyclic(Box::new(move |weak: &Weak<GlobalsMap>| {
1592 let mut builtins: GlobalsMap = FxHashMap::from_iter(builtins);
1595
1596 if enable_import {
1601 let import = Value::Builtin(import::builtins_import(weak, source.clone()));
1602 builtins.insert("import", import);
1603 }
1604
1605 let mut globals: GlobalsMap = FxHashMap::default();
1608
1609 let weak_globals = weak.clone();
1613 builtins.insert(
1614 "builtins",
1615 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1616 Ok(weak_globals
1617 .upgrade()
1618 .unwrap()
1619 .get("builtins")
1620 .cloned()
1621 .unwrap())
1622 }))),
1623 );
1624
1625 globals.insert("true", Value::Bool(true));
1627 globals.insert("false", Value::Bool(false));
1628 globals.insert("null", Value::Null);
1629
1630 builtins.extend(src_builtins.into_iter().map(move |(name, code)| {
1633 let compiled = compile_src_builtin(name, code, source.clone(), weak);
1634 (name, compiled)
1635 }));
1636
1637 globals.insert(
1640 "builtins",
1641 Value::attrs(NixAttrs::from_iter(builtins.clone())),
1642 );
1643
1644 for global in GLOBAL_BUILTINS {
1647 if let Some(builtin) = builtins.get(global).cloned() {
1648 globals.insert(global, builtin);
1649 }
1650 }
1651
1652 globals
1653 }))
1654}
1655
1656pub fn compile(
1657 expr: &ast::Expr,
1658 location: Option<PathBuf>,
1659 globals: Rc<GlobalsMap>,
1660 env: Option<&FxHashMap<SmolStr, Value>>,
1661 source: &SourceCode,
1662 file: &codemap::File,
1663 observer: &mut dyn CompilerObserver,
1664) -> EvalResult<CompilationOutput> {
1665 let mut c = Compiler::new(location, globals.clone(), env, source, file, observer)?;
1666
1667 let root_span = c.span_for(expr);
1668 let root_slot = c.scope_mut().declare_phantom(root_span, false);
1669 c.compile(root_slot, expr.clone());
1670
1671 c.emit_force(expr);
1676 if let Some(env) = env {
1677 if !env.is_empty() {
1678 c.push_op(Op::CloseScope, &root_span);
1679 c.push_uvarint(env.len() as u64);
1680 }
1681 }
1682 c.push_op(Op::Return, &root_span);
1683
1684 let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
1685 c.observer.observe_compiled_toplevel(&lambda);
1686
1687 Ok(CompilationOutput {
1688 lambda,
1689 warnings: c.warnings,
1690 errors: c.errors,
1691 })
1692}