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) {
919 self.compile(slot, node.condition().unwrap());
920 self.emit_force(&node.condition().unwrap());
921
922 let throw_idx = self.push_op(Op::JumpIfCatchable, &node.condition().unwrap());
923 self.push_u16(0);
924
925 let then_idx = self.push_op(Op::JumpIfFalse, &node.condition().unwrap());
926 self.push_u16(0);
927
928 self.push_op(Op::Pop, node); self.compile(slot, node.body().unwrap());
930
931 let else_idx = self.push_op(Op::Jump, node);
932 self.push_u16(0);
933
934 self.patch_jump(then_idx); self.push_op(Op::Pop, node); self.compile(slot, node.else_body().unwrap());
937
938 self.patch_jump(else_idx); self.patch_jump(throw_idx); }
941
942 fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
946 self.scope_mut().begin_scope();
947 self.compile(slot, node.namespace().unwrap());
951
952 let span = self.span_for(&node.namespace().unwrap());
953
954 let local_idx = self.scope_mut().declare_phantom(span, true);
960 let with_idx = self.scope().stack_index(local_idx);
961
962 self.scope_mut().push_with();
963
964 self.push_op(Op::PushWith, &node.namespace().unwrap());
965 self.push_uvarint(with_idx.0 as u64);
966
967 self.compile(slot, node.body().unwrap());
968
969 self.push_op(Op::PopWith, node);
970 self.scope_mut().pop_with();
971 self.cleanup_scope(node);
972 }
973
974 fn compile_param_pattern(&mut self, pattern: &ast::Pattern) -> (Formals, CodeIdx) {
1013 let span = self.span_for(pattern);
1014
1015 let (set_idx, pat_bind_name) = match pattern.pat_bind() {
1016 Some(name) => {
1017 let pat_bind_name = name.ident().unwrap().to_string();
1018 (
1019 self.declare_local(&name, pat_bind_name.clone()),
1020 Some(pat_bind_name),
1021 )
1022 }
1023 None => (self.scope_mut().declare_phantom(span, true), None),
1024 };
1025
1026 self.scope_mut().mark_initialised(set_idx);
1028 self.emit_force(pattern);
1029 let throw_idx = self.push_op(Op::JumpIfCatchable, pattern);
1030 self.push_u16(0);
1031
1032 self.push_op(Op::AssertAttrs, pattern);
1034
1035 let ellipsis = pattern.ellipsis_token().is_some();
1036 if !ellipsis {
1037 self.push_op(Op::ValidateClosedFormals, pattern);
1038 }
1039
1040 let mut entries: Vec<TrackedFormal> = vec![];
1044 let mut arguments = BTreeMap::default();
1045
1046 for entry in pattern.pat_entries() {
1047 let ident = entry.ident().unwrap();
1048 let idx = self.declare_local(&ident, ident.to_string());
1049
1050 arguments.insert(ident.into(), entry.default().is_some());
1051
1052 if let Some(default_expr) = entry.default() {
1053 entries.push(TrackedFormal::WithDefault {
1054 local_idx: idx,
1055 finalise_request_idx: {
1059 let span = self.span_for(&default_expr);
1060 self.scope_mut().declare_phantom(span, false)
1061 },
1062 default_expr,
1063 pattern_entry: entry,
1064 });
1065 } else {
1066 entries.push(TrackedFormal::NoDefault {
1067 local_idx: idx,
1068 pattern_entry: entry,
1069 });
1070 }
1071 }
1072
1073 let stack_idx = self.scope().stack_index(set_idx);
1076 for tracked_formal in entries.iter() {
1077 self.push_op(Op::GetLocal, pattern);
1078 self.push_uvarint(stack_idx.0 as u64);
1079 self.emit_literal_ident(&tracked_formal.pattern_entry().ident().unwrap());
1080
1081 let idx = tracked_formal.local_idx();
1082
1083 match tracked_formal {
1086 TrackedFormal::WithDefault {
1087 default_expr,
1088 pattern_entry,
1089 ..
1090 } => {
1091 self.push_op(Op::AttrsTrySelect, &pattern_entry.ident().unwrap());
1108 let jump_to_default = self.push_op(Op::JumpIfNotFound, default_expr);
1109 self.push_u16(0);
1110
1111 self.emit_constant(Value::FinaliseRequest(false), default_expr);
1112
1113 let jump_over_default = self.push_op(Op::Jump, default_expr);
1114 self.push_u16(0);
1115
1116 self.patch_jump(jump_to_default);
1117
1118 self.compile(idx, default_expr.clone());
1120
1121 self.emit_constant(Value::FinaliseRequest(true), default_expr);
1122
1123 self.patch_jump(jump_over_default);
1124 }
1125 TrackedFormal::NoDefault { pattern_entry, .. } => {
1126 self.push_op(Op::AttrsSelect, &pattern_entry.ident().unwrap());
1127 }
1128 }
1129
1130 self.scope_mut().mark_initialised(idx);
1131 if let TrackedFormal::WithDefault {
1132 finalise_request_idx,
1133 ..
1134 } = tracked_formal
1135 {
1136 self.scope_mut().mark_initialised(*finalise_request_idx);
1137 }
1138 }
1139
1140 for tracked_formal in entries.iter() {
1141 if self.scope()[tracked_formal.local_idx()].needs_finaliser {
1142 let stack_idx = self.scope().stack_index(tracked_formal.local_idx());
1143 match tracked_formal {
1144 TrackedFormal::NoDefault { .. } => panic!(
1145 "Tvix bug: local for pattern formal needs finaliser, but has no default expr"
1146 ),
1147 TrackedFormal::WithDefault {
1148 finalise_request_idx,
1149 ..
1150 } => {
1151 let finalise_request_stack_idx =
1152 self.scope().stack_index(*finalise_request_idx);
1153
1154 self.push_op(Op::GetLocal, pattern);
1156 self.push_uvarint(finalise_request_stack_idx.0 as u64);
1157 let jump_over_finalise = self.push_op(Op::JumpIfNoFinaliseRequest, pattern);
1158 self.push_u16(0);
1159 self.push_op(Op::Finalise, pattern);
1160 self.push_uvarint(stack_idx.0 as u64);
1161 self.patch_jump(jump_over_finalise);
1162 self.push_op(Op::Pop, pattern);
1164 }
1165 }
1166 }
1167 }
1168
1169 (
1170 (Formals {
1171 arguments,
1172 ellipsis,
1173 span,
1174 name: pat_bind_name,
1175 }),
1176 throw_idx,
1177 )
1178 }
1179
1180 fn compile_lambda(&mut self, slot: LocalIdx, node: &ast::Lambda) -> Option<CodeIdx> {
1181 let formals = match node.param().unwrap() {
1184 ast::Param::Pattern(pat) => Some(self.compile_param_pattern(&pat)),
1185
1186 ast::Param::IdentParam(param) => {
1187 let name = param
1188 .ident()
1189 .unwrap()
1190 .ident_token()
1191 .unwrap()
1192 .text()
1193 .to_string();
1194
1195 let idx = self.declare_local(¶m, &name);
1196 self.scope_mut().mark_initialised(idx);
1197 None
1198 }
1199 };
1200
1201 self.compile(slot, node.body().unwrap());
1202 if let Some((formals, throw_idx)) = formals {
1203 self.context_mut().lambda.formals = Some(formals);
1204 Some(throw_idx)
1205 } else {
1206 self.context_mut().lambda.formals = None;
1207 None
1208 }
1209 }
1210
1211 fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
1212 where
1213 N: ToSpan,
1214 F: FnOnce(&mut Compiler, LocalIdx),
1215 {
1216 self.compile_lambda_or_thunk(true, outer_slot, node, |comp, idx| {
1217 content(comp, idx);
1218 None
1219 })
1220 }
1221
1222 fn compile_lambda_or_thunk<N, F>(
1224 &mut self,
1225 is_suspended_thunk: bool,
1226 outer_slot: LocalIdx,
1227 node: &N,
1228 content: F,
1229 ) where
1230 N: ToSpan,
1231 F: FnOnce(&mut Compiler, LocalIdx) -> Option<CodeIdx>,
1232 {
1233 let name = self.scope()[outer_slot].name();
1234 self.new_context();
1235
1236 self.context_mut().lambda.name = name;
1239
1240 let span = self.span_for(node);
1241 let slot = self.scope_mut().declare_phantom(span, false);
1242 self.scope_mut().begin_scope();
1243
1244 let throw_idx = content(self, slot);
1245 self.cleanup_scope(node);
1246 if let Some(throw_idx) = throw_idx {
1247 self.patch_jump(throw_idx);
1248 }
1249
1250 let mut compiled = self.contexts.pop().unwrap();
1253
1254 compiled
1256 .lambda
1257 .chunk
1258 .push_op(Op::Return, self.span_for(node));
1259
1260 let lambda = Rc::new(compiled.lambda);
1261 if is_suspended_thunk {
1262 self.observer.observe_compiled_thunk(&lambda);
1263 } else {
1264 self.observer.observe_compiled_lambda(&lambda);
1265 }
1266
1267 if lambda.upvalue_count == 0 && !compiled.captures_with_stack {
1269 self.emit_constant(
1270 if is_suspended_thunk {
1271 Value::Thunk(Thunk::new_suspended(lambda, span))
1272 } else {
1273 Value::Closure(Rc::new(Closure::new(lambda)))
1274 },
1275 node,
1276 );
1277 return;
1278 }
1279
1280 let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
1285
1286 let code_idx = self.push_op(
1287 if is_suspended_thunk {
1288 Op::ThunkSuspended
1289 } else {
1290 Op::ThunkClosure
1291 },
1292 node,
1293 );
1294 self.push_uvarint(blueprint_idx.0 as u64);
1295
1296 self.emit_upvalue_data(
1297 outer_slot,
1298 node,
1299 compiled.scope.upvalues,
1300 compiled.captures_with_stack,
1301 );
1302
1303 if !is_suspended_thunk && !self.scope()[outer_slot].needs_finaliser {
1304 if !self.scope()[outer_slot].must_thunk {
1305 self.chunk().code[code_idx.0] = Op::Closure as u8;
1309 } else {
1310 #[cfg(debug_assertions)]
1316 {
1317 self.push_op(Op::Finalise, &self.span_for(node));
1318 self.push_uvarint(self.scope().stack_index(outer_slot).0 as u64);
1319 }
1320 }
1321 }
1322 }
1323
1324 fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
1325 self.compile(slot, node.argument().unwrap());
1330 self.compile(slot, node.lambda().unwrap());
1331 self.emit_force(&node.lambda().unwrap());
1332 self.push_op(Op::Call, node);
1333 }
1334
1335 fn emit_upvalue_data<T: ToSpan>(
1338 &mut self,
1339 slot: LocalIdx,
1340 _: &T, upvalues: Vec<Upvalue>,
1342 capture_with: bool,
1343 ) {
1344 let mut count = (upvalues.len() as u64) << 1;
1347 if capture_with {
1348 count |= 1;
1349 }
1350 self.push_uvarint(count);
1351
1352 for upvalue in upvalues {
1353 match upvalue.kind {
1354 UpvalueKind::Local(idx) => {
1355 let target = &self.scope()[idx];
1356 let stack_idx = self.scope().stack_index(idx);
1357
1358 if !target.initialised {
1361 self.push_uvarint(Position::deferred_local(stack_idx).0);
1362 self.scope_mut().mark_needs_finaliser(slot);
1363 } else {
1364 if slot == idx {
1366 self.scope_mut().mark_must_thunk(slot);
1367 }
1368 self.push_uvarint(Position::stack_index(stack_idx).0);
1369 }
1370 }
1371
1372 UpvalueKind::Upvalue(idx) => {
1373 self.push_uvarint(Position::upvalue_index(idx).0);
1374 }
1375 };
1376 }
1377 }
1378
1379 fn emit_literal_ident(&mut self, ident: &ast::Ident) {
1383 self.emit_constant(Value::String(ident.clone().into()), ident);
1384 }
1385
1386 fn patch_jump(&mut self, idx: CodeIdx) {
1393 self.chunk().patch_jump(idx.0);
1394 }
1395
1396 fn cleanup_scope<N: ToSpan>(&mut self, node: &N) {
1399 let (popcount, unused_spans) = self.scope_mut().end_scope();
1403
1404 for span in &unused_spans {
1405 self.emit_warning(span, WarningKind::UnusedBinding);
1406 }
1407
1408 if popcount > 0 {
1409 self.push_op(Op::CloseScope, node);
1410 self.push_uvarint(popcount as u64);
1411 }
1412 }
1413
1414 fn new_context(&mut self) {
1417 self.contexts.push(self.context().inherit());
1418 }
1419
1420 fn declare_local<S: Into<String>, N: ToSpan>(&mut self, node: &N, name: S) -> LocalIdx {
1424 let name = name.into();
1425 let depth = self.scope().scope_depth();
1426
1427 if let Some((global_ident, _)) = self.globals.get_key_value(name.as_str()) {
1430 self.emit_warning(node, WarningKind::ShadowedGlobal(global_ident));
1431 }
1432
1433 let span = self.span_for(node);
1434 let (idx, shadowed) = self.scope_mut().declare_local(name, span);
1435
1436 if let Some(shadow_idx) = shadowed {
1437 let other = &self.scope()[shadow_idx];
1438 if other.depth == depth {
1439 self.emit_error(node, ErrorKind::VariableAlreadyDefined(other.span));
1440 }
1441 }
1442
1443 idx
1444 }
1445
1446 fn has_dynamic_ancestor(&mut self) -> bool {
1451 let mut ancestor_has_with = false;
1452
1453 for ctx in self.contexts.iter_mut() {
1454 if ancestor_has_with {
1455 ctx.captures_with_stack = true;
1458 } else {
1459 ancestor_has_with = ctx.scope.has_with();
1461 }
1462 }
1463
1464 ancestor_has_with
1465 }
1466
1467 fn emit_force<N: ToSpan>(&mut self, node: &N) {
1468 self.push_op(Op::Force, node);
1469 }
1470
1471 fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind) {
1472 let span = self.span_for(node);
1473 self.warnings.push(EvalWarning { kind, span })
1474 }
1475
1476 fn emit_error<N: ToSpan>(&mut self, node: &N, kind: ErrorKind) {
1477 let span = self.span_for(node);
1478 self.errors
1479 .push(Error::new(kind, span, self.source.clone()))
1480 }
1481}
1482
1483fn expr_static_str(node: &ast::Str) -> Option<SmolStr> {
1485 let mut parts = node.normalized_parts();
1486
1487 if parts.len() != 1 {
1488 return None;
1489 }
1490
1491 if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
1492 return Some(SmolStr::new(lit));
1493 }
1494
1495 None
1496}
1497
1498fn expr_static_attr_str(node: &ast::Attr) -> Option<SmolStr> {
1501 match node {
1502 ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
1503 ast::Attr::Str(s) => expr_static_str(s),
1504
1505 ast::Attr::Dynamic(ref dynamic) => match dynamic.expr().unwrap() {
1510 ast::Expr::Str(s) => expr_static_str(&s),
1511 _ => None,
1512 },
1513 }
1514}
1515
1516fn compile_src_builtin(
1523 name: &'static str,
1524 code: &str,
1525 source: SourceCode,
1526 weak: &Weak<GlobalsMap>,
1527) -> Value {
1528 use std::fmt::Write;
1529
1530 let parsed = rnix::ast::Root::parse(code);
1531
1532 if !parsed.errors().is_empty() {
1533 let mut out = format!("BUG: code for source-builtin '{name}' had parser errors");
1534 for error in parsed.errors() {
1535 writeln!(out, "{error}").unwrap();
1536 }
1537
1538 panic!("{}", out);
1539 }
1540
1541 let file = source.add_file(format!("<src-builtins/{name}.nix>"), code.to_string());
1542 let weak = weak.clone();
1543
1544 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1545 let result = compile(
1546 &parsed.tree().expr().unwrap(),
1547 None,
1548 weak.upgrade().unwrap(),
1549 None,
1550 &source,
1551 &file,
1552 &mut crate::observer::NoOpObserver {},
1553 )
1554 .map_err(|e| ErrorKind::NativeError {
1555 gen_type: "derivation",
1556 err: Box::new(e),
1557 })?;
1558
1559 if !result.errors.is_empty() {
1560 return Err(ErrorKind::ImportCompilerError {
1561 path: format!("src-builtins/{name}.nix").into(),
1562 errors: result.errors,
1563 });
1564 }
1565
1566 Ok(Value::Thunk(Thunk::new_suspended(result.lambda, file.span)))
1567 })))
1568}
1569
1570pub fn prepare_globals(
1579 builtins: Vec<(&'static str, Value)>,
1580 src_builtins: Vec<(&'static str, &'static str)>,
1581 source: SourceCode,
1582 enable_import: bool,
1583) -> Rc<GlobalsMap> {
1584 Rc::new_cyclic(Box::new(move |weak: &Weak<GlobalsMap>| {
1585 let mut builtins: GlobalsMap = FxHashMap::from_iter(builtins);
1588
1589 if enable_import {
1594 let import = Value::Builtin(import::builtins_import(weak, source.clone()));
1595 builtins.insert("import", import);
1596 }
1597
1598 let mut globals: GlobalsMap = FxHashMap::default();
1601
1602 let weak_globals = weak.clone();
1606 builtins.insert(
1607 "builtins",
1608 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1609 Ok(weak_globals
1610 .upgrade()
1611 .unwrap()
1612 .get("builtins")
1613 .cloned()
1614 .unwrap())
1615 }))),
1616 );
1617
1618 globals.insert("true", Value::Bool(true));
1620 globals.insert("false", Value::Bool(false));
1621 globals.insert("null", Value::Null);
1622
1623 builtins.extend(src_builtins.into_iter().map(move |(name, code)| {
1626 let compiled = compile_src_builtin(name, code, source.clone(), weak);
1627 (name, compiled)
1628 }));
1629
1630 globals.insert(
1633 "builtins",
1634 Value::attrs(NixAttrs::from_iter(builtins.clone())),
1635 );
1636
1637 for global in GLOBAL_BUILTINS {
1640 if let Some(builtin) = builtins.get(global).cloned() {
1641 globals.insert(global, builtin);
1642 }
1643 }
1644
1645 globals
1646 }))
1647}
1648
1649pub fn compile(
1650 expr: &ast::Expr,
1651 location: Option<PathBuf>,
1652 globals: Rc<GlobalsMap>,
1653 env: Option<&FxHashMap<SmolStr, Value>>,
1654 source: &SourceCode,
1655 file: &codemap::File,
1656 observer: &mut dyn CompilerObserver,
1657) -> EvalResult<CompilationOutput> {
1658 let mut c = Compiler::new(location, globals.clone(), env, source, file, observer)?;
1659
1660 let root_span = c.span_for(expr);
1661 let root_slot = c.scope_mut().declare_phantom(root_span, false);
1662 c.compile(root_slot, expr.clone());
1663
1664 c.emit_force(expr);
1669 if let Some(env) = env {
1670 if !env.is_empty() {
1671 c.push_op(Op::CloseScope, &root_span);
1672 c.push_uvarint(env.len() as u64);
1673 }
1674 }
1675 c.push_op(Op::Return, &root_span);
1676
1677 let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
1678 c.observer.observe_compiled_toplevel(&lambda);
1679
1680 Ok(CompilationOutput {
1681 lambda,
1682 warnings: c.warnings,
1683 errors: c.errors,
1684 })
1685}