1mod 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
43pub struct CompilationOutput {
47 pub lambda: Rc<Lambda>,
48 pub warnings: Vec<EvalWarning>,
49 pub errors: Vec<Error>,
50}
51
52struct 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
77enum TrackedFormal {
89 NoDefault {
90 local_idx: LocalIdx,
91 pattern_entry: ast::PatEntry,
92 },
93 WithDefault {
94 local_idx: LocalIdx,
95 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
118pub type GlobalsMap = FxHashMap<&'static str, Value>;
121
122const 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 globals: Rc<GlobalsMap>,
160
161 source: &'source SourceCode,
164
165 file: &'source codemap::File,
168
169 observer: OptionalCompilerObserver<'observer>,
172
173 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
185impl<'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 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
245impl 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 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 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 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
351impl 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 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 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 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 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 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 fn compile_str_parts(
535 &mut self,
536 slot: LocalIdx,
537 parent_node: &ast::Str,
538 parts: Vec<ast::InterpolPart<String>>,
539 ) {
540 for part in parts.iter().rev() {
545 match part {
546 ast::InterpolPart::Interpolation(ipol) => {
551 self.compile(slot, ipol.expr().unwrap());
552 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 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 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 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 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 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 let end_idx = self.push_op(Op::JumpIfFalse, node);
680 self.push_u16(0);
681
682 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 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 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 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 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 fn compile_list(&mut self, slot: LocalIdx, node: &ast::List) {
755 let mut count = 0;
756
757 let scope_guard = self.scope_mut().begin_scope("compile_list");
760
761 for item in node.items() {
762 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 self.compile(slot, node.expr().unwrap());
802 self.emit_force(node);
803
804 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 self.push_op(Op::HasAttr, node);
818 }
819
820 fn optimise_select(&mut self, path: &ast::Attrpath) -> bool {
831 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 if let (Some(attr), None) = (path_iter.next(), path_iter.next()) {
851 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 self.compile(slot, set.clone());
875 if self.optimise_select(&path) {
876 return;
877 }
878
879 for fragment in path.attrs() {
884 self.emit_force(&set);
886
887 self.compile_attr(slot, &fragment);
888 self.push_op(Op::AttrsSelect, &fragment);
889 }
890 }
891
892 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 self.compile(slot, default);
953 self.patch_jump(final_jump);
954 }
955
956 fn compile_assert(&mut self, slot: LocalIdx, node: &ast::Assert) {
969 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 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); 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); self.push_op(Op::Pop, node); self.compile(slot, node.else_body().unwrap());
1025
1026 self.patch_jump(else_idx); self.patch_jump(throw_idx); }
1029
1030 fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
1034 let scope_guard = self.scope_mut().begin_scope("compile_with");
1035 self.compile(slot, node.namespace().unwrap());
1039
1040 let span = self.span_for(&node.namespace().unwrap());
1041
1042 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 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 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 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 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 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 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 match tracked_formal {
1174 TrackedFormal::WithDefault {
1175 default_expr,
1176 pattern_entry,
1177 ..
1178 } => {
1179 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 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 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 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 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(¶m, &name);
1284 self.scope_mut().mark_initialised(idx);
1285
1286 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 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 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 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 let mut compiled = self.contexts.pop().unwrap();
1349
1350 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 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 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 self.chunk().code[code_idx.0] = Op::Closure as u8;
1405 } else {
1406 #[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 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 fn emit_upvalue_data<T: ToSpan>(
1434 &mut self,
1435 slot: LocalIdx,
1436 _: &T, upvalues: Vec<Upvalue>,
1438 capture_with: bool,
1439 ) {
1440 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 !target.initialised {
1454 self.push_uvarint(Position::deferred_local(stack_idx).0);
1455 self.scope_mut().mark_needs_finaliser(slot);
1456 } else {
1457 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 fn emit_literal_ident(&mut self, ident: &ast::Ident) {
1498 self.emit_constant(Value::String(ident.clone().into()), ident);
1499 }
1500
1501 fn patch_jump(&mut self, idx: CodeIdx) {
1508 self.chunk().patch_jump(idx.0);
1509 }
1510
1511 fn cleanup_scope<N: ToSpan>(&mut self, node: &N, guard: ScopeGuard) {
1514 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 fn new_context(&mut self) {
1532 self.contexts.push(self.context().inherit());
1533 }
1534
1535 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 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 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 ctx.captures_with_stack = true;
1573 } else {
1574 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
1598fn 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
1613fn 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 ast::Attr::Dynamic(dynamic) => match dynamic.expr().unwrap() {
1625 ast::Expr::Str(s) => expr_static_str(&s),
1626 _ => None,
1627 },
1628 }
1629}
1630
1631fn is_interpolated_path(parts: &[InterpolPart<PathContent>]) -> bool {
1633 parts
1634 .iter()
1635 .any(|part| matches!(part, ast::InterpolPart::Interpolation(_)))
1636}
1637
1638fn 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
1692pub 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 let mut builtins: GlobalsMap = FxHashMap::from_iter(builtins);
1710
1711 if enable_import {
1716 let import = Value::Builtin(import::builtins_import(weak, source.clone()));
1717 builtins.insert("import", import);
1718 }
1719
1720 let mut globals: GlobalsMap = FxHashMap::default();
1723
1724 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 globals.insert("true", Value::Bool(true));
1742 globals.insert("false", Value::Bool(false));
1743 globals.insert("null", Value::Null);
1744
1745 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 globals.insert(
1755 "builtins",
1756 Value::attrs(NixAttrs::from_iter(builtins.clone())),
1757 );
1758
1759 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 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}