1mod bindings;
17mod import;
18mod optimiser;
19mod scope;
20
21use codemap::Span;
22use rnix::ast::{self, AstToken, InterpolPart, PathContent};
23use rustc_hash::FxHashMap;
24use smol_str::SmolStr;
25use std::collections::BTreeMap;
26use std::path::PathBuf;
27use std::rc::{Rc, Weak};
28
29use crate::SourceCode;
30use crate::chunk::Chunk;
31use crate::errors::{Error, ErrorKind, EvalResult};
32use crate::observer::{CompilerObserver, OptionalCompilerObserver};
33use crate::opcode::{CodeIdx, Op, Position, UpvalueIdx};
34use crate::spans::ToSpan;
35use crate::upvalues::UpvalueData;
36use crate::value::{Closure, Formals, Lambda, NixAttrs, Thunk, Value};
37use crate::warnings::{EvalWarning, WarningKind};
38use crate::{CoercionKind, NixString};
39
40use self::scope::{LocalIdx, LocalPosition, Scope, Upvalue, UpvalueKind};
41
42pub struct CompilationOutput {
46 pub lambda: Rc<Lambda>,
47 pub warnings: Vec<EvalWarning>,
48 pub errors: Vec<Error>,
49}
50
51struct LambdaCtx {
53 lambda: Lambda,
54 scope: Scope,
55 captures_with_stack: bool,
56}
57
58impl LambdaCtx {
59 fn new() -> Self {
60 LambdaCtx {
61 lambda: Lambda::default(),
62 scope: Default::default(),
63 captures_with_stack: false,
64 }
65 }
66
67 fn inherit(&self) -> Self {
68 LambdaCtx {
69 lambda: Lambda::default(),
70 scope: self.scope.inherit(),
71 captures_with_stack: false,
72 }
73 }
74}
75
76enum TrackedFormal {
88 NoDefault {
89 local_idx: LocalIdx,
90 pattern_entry: ast::PatEntry,
91 },
92 WithDefault {
93 local_idx: LocalIdx,
94 finalise_request_idx: LocalIdx,
97 default_expr: ast::Expr,
98 pattern_entry: ast::PatEntry,
99 },
100}
101
102impl TrackedFormal {
103 fn pattern_entry(&self) -> &ast::PatEntry {
104 match self {
105 TrackedFormal::NoDefault { pattern_entry, .. } => pattern_entry,
106 TrackedFormal::WithDefault { pattern_entry, .. } => pattern_entry,
107 }
108 }
109 fn local_idx(&self) -> LocalIdx {
110 match self {
111 TrackedFormal::NoDefault { local_idx, .. } => *local_idx,
112 TrackedFormal::WithDefault { local_idx, .. } => *local_idx,
113 }
114 }
115}
116
117pub type GlobalsMap = FxHashMap<&'static str, Value>;
120
121const GLOBAL_BUILTINS: &[&str] = &[
126 "abort",
127 "baseNameOf",
128 "derivation",
129 "derivationStrict",
130 "dirOf",
131 "fetchGit",
132 "fetchMercurial",
133 "fetchTarball",
134 "fromTOML",
135 "import",
136 "isNull",
137 "map",
138 "placeholder",
139 "removeAttrs",
140 "scopedImport",
141 "throw",
142 "toString",
143 "__curPos",
144];
145
146pub struct Compiler<'source, 'observer> {
147 contexts: Vec<LambdaCtx>,
148 warnings: Vec<EvalWarning>,
149 errors: Vec<Error>,
150 root_dir: PathBuf,
151
152 globals: Rc<GlobalsMap>,
159
160 source: &'source SourceCode,
163
164 file: &'source codemap::File,
167
168 observer: OptionalCompilerObserver<'observer>,
171
172 dead_scope: usize,
176}
177
178impl Compiler<'_, '_> {
179 pub(super) fn span_for<S: ToSpan>(&self, to_span: &S) -> Span {
180 to_span.span_for(self.file)
181 }
182}
183
184impl<'source, 'observer> Compiler<'source, 'observer> {
186 pub(crate) fn new(
187 location: Option<PathBuf>,
188 globals: Rc<GlobalsMap>,
189 env: Option<&FxHashMap<SmolStr, Value>>,
190 source: &'source SourceCode,
191 file: &'source codemap::File,
192 observer: OptionalCompilerObserver<'observer>,
193 ) -> EvalResult<Self> {
194 let mut root_dir = match location {
195 Some(dir) if cfg!(target_arch = "wasm32") || dir.is_absolute() => Ok(dir),
196 _ => {
197 let current_dir = std::env::current_dir().map_err(|e| {
198 Error::new(
199 ErrorKind::RelativePathResolution(format!(
200 "could not determine current directory: {e}"
201 )),
202 file.span,
203 source.clone(),
204 )
205 })?;
206 if let Some(dir) = location {
207 Ok(current_dir.join(dir))
208 } else {
209 Ok(current_dir)
210 }
211 }
212 }?;
213
214 if root_dir.is_file() {
218 root_dir.pop();
219 }
220
221 #[cfg(not(target_arch = "wasm32"))]
222 debug_assert!(root_dir.is_absolute());
223
224 let mut compiler = Self {
225 root_dir,
226 source,
227 file,
228 observer,
229 globals,
230 contexts: vec![LambdaCtx::new()],
231 warnings: vec![],
232 errors: vec![],
233 dead_scope: 0,
234 };
235
236 if let Some(env) = env {
237 compiler.compile_env(env);
238 }
239
240 Ok(compiler)
241 }
242}
243
244impl Compiler<'_, '_> {
247 fn context(&self) -> &LambdaCtx {
248 &self.contexts[self.contexts.len() - 1]
249 }
250
251 fn context_mut(&mut self) -> &mut LambdaCtx {
252 let idx = self.contexts.len() - 1;
253 &mut self.contexts[idx]
254 }
255
256 fn chunk(&mut self) -> &mut Chunk {
257 &mut self.context_mut().lambda.chunk
258 }
259
260 fn scope(&self) -> &Scope {
261 &self.context().scope
262 }
263
264 fn scope_mut(&mut self) -> &mut Scope {
265 &mut self.context_mut().scope
266 }
267
268 fn push_op<T: ToSpan>(&mut self, data: Op, node: &T) -> CodeIdx {
271 if self.dead_scope > 0 {
272 return CodeIdx(0);
273 }
274
275 let span = self.span_for(node);
276 CodeIdx(self.chunk().push_op(data, span))
277 }
278
279 fn push_u8(&mut self, data: u8) {
280 if self.dead_scope > 0 {
281 return;
282 }
283
284 self.chunk().code.push(data);
285 }
286
287 fn push_uvarint(&mut self, data: u64) {
288 if self.dead_scope > 0 {
289 return;
290 }
291
292 self.chunk().push_uvarint(data);
293 }
294
295 fn push_u16(&mut self, data: u16) {
296 if self.dead_scope > 0 {
297 return;
298 }
299
300 self.chunk().push_u16(data);
301 }
302
303 pub(super) fn emit_constant<T: ToSpan>(&mut self, value: Value, node: &T) {
306 if self.dead_scope > 0 {
307 return;
308 }
309
310 let idx = self.chunk().push_constant(value);
311 self.push_op(Op::Constant, node);
312 self.push_uvarint(idx.0 as u64);
313 }
314
315 pub(super) fn emit_path_ipol_parts<T: ToSpan>(
319 &mut self,
320 slot: LocalIdx,
321 node: &T,
322 parts: impl DoubleEndedIterator<Item = InterpolPart<PathContent>>,
323 ) {
324 for part in parts.rev() {
325 match part {
326 InterpolPart::Interpolation(ipol) => {
327 self.compile(slot, ipol.expr().unwrap());
328 self.push_op(Op::CoerceToString, &ipol);
329 let encoded: u8 = CoercionKind {
330 strong: false,
331 import_paths: true,
332 }
333 .into();
334 self.push_u8(encoded);
335 }
336 InterpolPart::Literal(content) => {
337 self.emit_constant(Value::String(content.text().into()), node);
338 }
339 }
340 }
341 }
342}
343
344impl Compiler<'_, '_> {
346 fn compile(&mut self, slot: LocalIdx, expr: ast::Expr) {
347 let expr = optimiser::optimise_expr(self, slot, expr);
348
349 match &expr {
350 ast::Expr::Literal(literal) => self.compile_literal(literal),
351 ast::Expr::PathAbs(path) => self.compile_abs_path(slot, path),
352 ast::Expr::PathHome(path) => self.compile_home_path(slot, path),
353 ast::Expr::PathRel(path) => self.compile_rel_path(slot, path),
354 ast::Expr::PathSearch(path) => self.compile_search_path(slot, path),
355 ast::Expr::Str(s) => self.compile_str(slot, s),
356
357 ast::Expr::UnaryOp(op) => self.thunk(slot, op, move |c, s| c.compile_unary_op(s, op)),
358
359 ast::Expr::BinOp(binop) => {
360 self.thunk(slot, binop, move |c, s| c.compile_binop(s, binop))
361 }
362
363 ast::Expr::HasAttr(has_attr) => {
364 self.thunk(slot, has_attr, move |c, s| c.compile_has_attr(s, has_attr))
365 }
366
367 ast::Expr::List(list) => self.thunk(slot, list, move |c, s| c.compile_list(s, list)),
368
369 ast::Expr::AttrSet(attrs) => {
370 self.thunk(slot, attrs, move |c, s| c.compile_attr_set(s, attrs))
371 }
372
373 ast::Expr::Select(select) => {
374 self.thunk(slot, select, move |c, s| c.compile_select(s, select))
375 }
376
377 ast::Expr::Assert(assert) => {
378 self.thunk(slot, assert, move |c, s| c.compile_assert(s, assert))
379 }
380 ast::Expr::IfElse(if_else) => {
381 self.thunk(slot, if_else, move |c, s| c.compile_if_else(s, if_else))
382 }
383
384 ast::Expr::LetIn(let_in) => {
385 self.thunk(slot, let_in, move |c, s| c.compile_let_in(s, let_in))
386 }
387
388 ast::Expr::Ident(ident) => self.compile_ident(slot, ident),
389 ast::Expr::With(with) => self.thunk(slot, with, |c, s| c.compile_with(s, with)),
390 ast::Expr::Lambda(lambda) => self.thunk(slot, lambda, move |c, s| {
391 c.compile_lambda_or_thunk(false, s, lambda, |c, s| c.compile_lambda(s, lambda))
392 }),
393 ast::Expr::Apply(apply) => {
394 self.thunk(slot, apply, move |c, s| c.compile_apply(s, apply))
395 }
396
397 ast::Expr::Paren(paren) => self.compile(slot, paren.expr().unwrap()),
400
401 ast::Expr::LegacyLet(legacy_let) => self.thunk(slot, legacy_let, move |c, s| {
402 c.compile_legacy_let(s, legacy_let)
403 }),
404
405 ast::Expr::CurPos(curpos) => self.compile_cur_pos(curpos),
406
407 ast::Expr::Root(_) => unreachable!("there cannot be more than one root"),
408 ast::Expr::Error(_) => unreachable!("compile is only called on validated trees"),
409 }
410 }
411
412 fn compile_dead_code(&mut self, slot: LocalIdx, node: ast::Expr) {
419 self.dead_scope += 1;
420 self.compile(slot, node);
421 self.dead_scope -= 1;
422 }
423
424 fn compile_literal(&mut self, node: &ast::Literal) {
425 let value = match node.kind() {
426 ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()),
427 ast::LiteralKind::Integer(i) => match i.value() {
428 Ok(v) => Value::Integer(v),
429 Err(err) => return self.emit_error(node, err.into()),
430 },
431
432 ast::LiteralKind::Uri(u) => {
433 self.emit_warning(node, WarningKind::DeprecatedLiteralURL);
434 Value::from(u.syntax().text())
435 }
436 };
437
438 self.emit_constant(value, node);
439 }
440
441 fn compile_abs_path(&mut self, slot: LocalIdx, node: &ast::PathAbs) {
442 let parts = node.parts();
443
444 if is_interpolated_path(&parts) {
445 self.thunk(slot, node, move |c, s| {
446 let len = parts.len();
447 c.emit_path_ipol_parts(s, node, parts.into_iter());
448 c.push_op(Op::InterpolatePath, node);
449 c.push_uvarint(len as u64);
450 });
451 return;
452 }
453
454 let path = PathBuf::from(node.to_string());
457 let value = Value::Path(Box::new(crate::value::canon_path(path)));
458 self.emit_constant(value, node);
459 }
460
461 fn compile_home_path(&mut self, slot: LocalIdx, node: &ast::PathHome) {
462 let parts = node.parts();
463 let home_subpath = match &parts[0] {
464 ast::InterpolPart::Literal(part) => &part.text()[2..].to_string(),
465 _ => {
466 unreachable!("a home path can't start with interpolation")
469 }
470 };
471
472 if is_interpolated_path(&parts) {
473 self.thunk(slot, node, move |c, s| {
474 let len = parts.len();
475 c.emit_path_ipol_parts(s, node, parts.into_iter().skip(1));
476 c.emit_constant(Value::UnresolvedPath(Box::new(home_subpath.into())), node);
477 c.push_op(Op::ResolveHomePath, node);
478
479 c.push_op(Op::InterpolatePath, node);
480 c.push_uvarint(len as u64);
481 });
482 return;
483 }
484
485 self.emit_constant(Value::UnresolvedPath(Box::new(home_subpath.into())), node);
486 self.push_op(Op::ResolveHomePath, node);
487 }
488
489 fn compile_rel_path(&mut self, slot: LocalIdx, node: &ast::PathRel) {
490 let parts = node.parts();
491 let abs = match &parts[0] {
492 ast::InterpolPart::Literal(part) => self.root_dir.join(part.text()),
493 _ => {
494 unreachable!("a relative path can't start with interpolation");
495 }
496 };
497
498 if is_interpolated_path(&parts) {
499 self.thunk(slot, node, move |c, s| {
500 let len = parts.len();
501 c.emit_path_ipol_parts(s, node, parts.into_iter().skip(1));
502 c.emit_constant(Value::Path(abs.into()), node);
503
504 c.push_op(Op::InterpolatePath, node);
505 c.push_uvarint(len as u64);
506 });
507 return;
508 }
509
510 let value = Value::Path(Box::new(crate::value::canon_path(abs)));
511 self.emit_constant(value, node);
512 }
513
514 fn compile_search_path(&mut self, slot: LocalIdx, node: &ast::PathSearch) {
515 let raw_path = node.to_string();
516 let path = &raw_path[1..(raw_path.len() - 1)];
517 self.thunk(slot, node, move |c, _| {
519 c.emit_constant(Value::UnresolvedPath(Box::new(path.into())), node);
520 c.push_op(Op::FindFile, node);
521 });
522 }
523
524 fn compile_str_parts(
528 &mut self,
529 slot: LocalIdx,
530 parent_node: &ast::Str,
531 parts: Vec<ast::InterpolPart<String>>,
532 ) {
533 for part in parts.iter().rev() {
538 match part {
539 ast::InterpolPart::Interpolation(ipol) => {
544 self.compile(slot, ipol.expr().unwrap());
545 self.push_op(Op::CoerceToString, ipol);
547
548 let encoded: u8 = CoercionKind {
549 strong: false,
550 import_paths: true,
551 }
552 .into();
553
554 self.push_u8(encoded);
555 }
556
557 ast::InterpolPart::Literal(lit) => {
558 self.emit_constant(Value::from(lit.as_str()), parent_node);
559 }
560 }
561 }
562
563 if parts.len() != 1 {
564 self.push_op(Op::Interpolate, parent_node);
565 self.push_uvarint(parts.len() as u64);
566 }
567 }
568
569 fn compile_str(&mut self, slot: LocalIdx, node: &ast::Str) {
570 let parts = node.normalized_parts();
571
572 if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) {
578 self.thunk(slot, node, move |c, s| {
579 c.compile_str_parts(s, node, parts);
580 });
581 } else {
582 self.compile_str_parts(slot, node, parts);
583 }
584 }
585
586 fn compile_unary_op(&mut self, slot: LocalIdx, op: &ast::UnaryOp) {
587 self.compile(slot, op.expr().unwrap());
588 self.emit_force(op);
589
590 let opcode = match op.operator().unwrap() {
591 ast::UnaryOpKind::Invert => Op::Invert,
592 ast::UnaryOpKind::Negate => Op::Negate,
593 };
594
595 self.push_op(opcode, op);
596 }
597
598 fn compile_binop(&mut self, slot: LocalIdx, op: &ast::BinOp) {
599 use ast::BinOpKind;
600
601 match op.operator().unwrap() {
602 BinOpKind::And => return self.compile_and(slot, op),
607 BinOpKind::Or => return self.compile_or(slot, op),
608 BinOpKind::Implication => return self.compile_implication(slot, op),
609
610 BinOpKind::PipeRight | BinOpKind::PipeLeft => {
613 return self.emit_error(
614 op,
615 ErrorKind::NotImplemented("pipe operators not implemented"),
616 );
617 }
618
619 _ => {}
620 };
621
622 self.compile(slot, op.lhs().unwrap());
626 self.emit_force(&op.lhs().unwrap());
627
628 self.compile(slot, op.rhs().unwrap());
629 self.emit_force(&op.rhs().unwrap());
630
631 match op.operator().unwrap() {
632 BinOpKind::Add => self.push_op(Op::Add, op),
633 BinOpKind::Sub => self.push_op(Op::Sub, op),
634 BinOpKind::Mul => self.push_op(Op::Mul, op),
635 BinOpKind::Div => self.push_op(Op::Div, op),
636 BinOpKind::Update => self.push_op(Op::AttrsUpdate, op),
637 BinOpKind::Equal => self.push_op(Op::Equal, op),
638 BinOpKind::Less => self.push_op(Op::Less, op),
639 BinOpKind::LessOrEq => self.push_op(Op::LessOrEq, op),
640 BinOpKind::More => self.push_op(Op::More, op),
641 BinOpKind::MoreOrEq => self.push_op(Op::MoreOrEq, op),
642 BinOpKind::Concat => self.push_op(Op::Concat, op),
643 BinOpKind::NotEqual => {
644 self.push_op(Op::Equal, op);
645 self.push_op(Op::Invert, op)
646 }
647 BinOpKind::And
648 | BinOpKind::Implication
649 | BinOpKind::Or
650 | BinOpKind::PipeRight
651 | BinOpKind::PipeLeft => {
652 unreachable!()
653 }
654 };
655 }
656
657 fn compile_and(&mut self, slot: LocalIdx, node: &ast::BinOp) {
658 debug_assert!(
659 matches!(node.operator(), Some(ast::BinOpKind::And)),
660 "compile_and called with wrong operator kind: {:?}",
661 node.operator(),
662 );
663
664 self.compile(slot, node.lhs().unwrap());
666 self.emit_force(&node.lhs().unwrap());
667
668 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
669 self.push_u16(0);
670 let end_idx = self.push_op(Op::JumpIfFalse, node);
673 self.push_u16(0);
674
675 self.push_op(Op::Pop, node);
679 self.compile(slot, node.rhs().unwrap());
680 self.emit_force(&node.rhs().unwrap());
681
682 self.patch_jump(end_idx);
683 self.push_op(Op::AssertBool, node);
684 self.patch_jump(throw_idx);
685 }
686
687 fn compile_or(&mut self, slot: LocalIdx, node: &ast::BinOp) {
688 debug_assert!(
689 matches!(node.operator(), Some(ast::BinOpKind::Or)),
690 "compile_or called with wrong operator kind: {:?}",
691 node.operator(),
692 );
693
694 self.compile(slot, node.lhs().unwrap());
696 self.emit_force(&node.lhs().unwrap());
697
698 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
699 self.push_u16(0);
700 let end_idx = self.push_op(Op::JumpIfTrue, node);
703 self.push_u16(0);
704 self.push_op(Op::Pop, node);
705 self.compile(slot, node.rhs().unwrap());
706 self.emit_force(&node.rhs().unwrap());
707
708 self.patch_jump(end_idx);
709 self.push_op(Op::AssertBool, node);
710 self.patch_jump(throw_idx);
711 }
712
713 fn compile_implication(&mut self, slot: LocalIdx, node: &ast::BinOp) {
714 debug_assert!(
715 matches!(node.operator(), Some(ast::BinOpKind::Implication)),
716 "compile_implication called with wrong operator kind: {:?}",
717 node.operator(),
718 );
719
720 self.compile(slot, node.lhs().unwrap());
722 self.emit_force(&node.lhs().unwrap());
723 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
724 self.push_u16(0);
725 self.push_op(Op::Invert, node);
726
727 let end_idx = self.push_op(Op::JumpIfTrue, node);
729 self.push_u16(0);
730
731 self.push_op(Op::Pop, node);
732 self.compile(slot, node.rhs().unwrap());
733 self.emit_force(&node.rhs().unwrap());
734
735 self.patch_jump(end_idx);
736 self.push_op(Op::AssertBool, node);
737 self.patch_jump(throw_idx);
738 }
739
740 fn compile_list(&mut self, slot: LocalIdx, node: &ast::List) {
748 let mut count = 0;
749
750 self.scope_mut().begin_scope();
753
754 for item in node.items() {
755 let item_slot = match count {
759 0 => slot,
760 _ => {
761 let item_span = self.span_for(&item);
762 self.scope_mut().declare_phantom(item_span, false)
763 }
764 };
765
766 count += 1;
767 self.compile(item_slot, item);
768 self.scope_mut().mark_initialised(item_slot);
769 }
770
771 self.push_op(Op::List, node);
772 self.push_uvarint(count as u64);
773 self.scope_mut().end_scope();
774 }
775
776 fn compile_attr(&mut self, slot: LocalIdx, node: &ast::Attr) {
777 match node {
778 ast::Attr::Dynamic(dynamic) => {
779 self.compile(slot, dynamic.expr().unwrap());
780 self.emit_force(&dynamic.expr().unwrap());
781 }
782
783 ast::Attr::Str(s) => {
784 self.compile_str(slot, s);
785 self.emit_force(s);
786 }
787
788 ast::Attr::Ident(ident) => self.emit_literal_ident(ident),
789 }
790 }
791
792 fn compile_has_attr(&mut self, slot: LocalIdx, node: &ast::HasAttr) {
793 self.compile(slot, node.expr().unwrap());
795 self.emit_force(node);
796
797 for (count, fragment) in node.attrpath().unwrap().attrs().enumerate() {
800 if count > 0 {
801 self.push_op(Op::AttrsTrySelect, &fragment);
802 self.emit_force(&fragment);
803 }
804
805 self.compile_attr(slot, &fragment);
806 }
807
808 self.push_op(Op::HasAttr, node);
811 }
812
813 fn optimise_select(&mut self, path: &ast::Attrpath) -> bool {
824 if let Some((Op::Constant, op_idx)) = self.chunk().last_op() {
835 let (idx, _) = self.chunk().read_uvarint(op_idx + 1);
836 let constant = &mut self.chunk().constants[idx as usize];
837 if let Value::Attrs(attrs) = constant {
838 let mut path_iter = path.attrs();
839
840 if let (Some(attr), None) = (path_iter.next(), path_iter.next()) {
844 if let Some(ident) = expr_static_attr_str(&attr)
846 && let Some(selected_value) = attrs.select(ident.as_bytes())
847 {
848 *constant = selected_value.clone();
849 return true;
850 }
851 }
852 }
853 }
854
855 false
856 }
857
858 fn compile_select(&mut self, slot: LocalIdx, node: &ast::Select) {
859 let set = node.expr().unwrap();
860 let path = node.attrpath().unwrap();
861
862 if node.or_token().is_some() {
863 return self.compile_select_or(slot, set, path, node.default_expr().unwrap());
864 }
865
866 self.compile(slot, set.clone());
868 if self.optimise_select(&path) {
869 return;
870 }
871
872 for fragment in path.attrs() {
877 self.emit_force(&set);
879
880 self.compile_attr(slot, &fragment);
881 self.push_op(Op::AttrsSelect, &fragment);
882 }
883 }
884
885 fn compile_select_or(
915 &mut self,
916 slot: LocalIdx,
917 set: ast::Expr,
918 path: ast::Attrpath,
919 default: ast::Expr,
920 ) {
921 self.compile(slot, set);
922 if self.optimise_select(&path) {
923 return;
924 }
925
926 let mut jumps = vec![];
927
928 for fragment in path.attrs() {
929 self.emit_force(&fragment);
930 self.compile_attr(slot, &fragment.clone());
931 self.push_op(Op::AttrsTrySelect, &fragment);
932 jumps.push(self.push_op(Op::JumpIfNotFound, &fragment));
933 self.push_u16(0);
934 }
935
936 let final_jump = self.push_op(Op::Jump, &path);
937 self.push_u16(0);
938
939 for jump in jumps {
940 self.patch_jump(jump);
941 }
942
943 self.compile(slot, default);
946 self.patch_jump(final_jump);
947 }
948
949 fn compile_assert(&mut self, slot: LocalIdx, node: &ast::Assert) {
962 self.compile(slot, node.condition().unwrap());
964 self.emit_force(&node.condition().unwrap());
965
966 let throw_idx = self.push_op(Op::JumpIfCatchable, node);
967 self.push_u16(0);
968
969 let then_idx = self.push_op(Op::JumpIfFalse, node);
970 self.push_u16(0);
971
972 self.push_op(Op::Pop, node);
973 self.compile(slot, node.body().unwrap());
974
975 let else_idx = self.push_op(Op::Jump, node);
976 self.push_u16(0);
977
978 self.patch_jump(then_idx);
979 self.push_op(Op::Pop, node);
980 self.push_op(Op::AssertFail, &node.condition().unwrap());
981
982 self.patch_jump(else_idx);
983 self.patch_jump(throw_idx);
984 }
985
986 fn compile_if_else(&mut self, slot: LocalIdx, node: &ast::IfElse) {
1000 self.compile(slot, node.condition().unwrap());
1001 self.emit_force(&node.condition().unwrap());
1002
1003 let throw_idx = self.push_op(Op::JumpIfCatchable, &node.condition().unwrap());
1004 self.push_u16(0);
1005
1006 let then_idx = self.push_op(Op::JumpIfFalse, &node.condition().unwrap());
1007 self.push_u16(0);
1008
1009 self.push_op(Op::Pop, node); self.compile(slot, node.body().unwrap());
1011
1012 let else_idx = self.push_op(Op::Jump, node);
1013 self.push_u16(0);
1014
1015 self.patch_jump(then_idx); self.push_op(Op::Pop, node); self.compile(slot, node.else_body().unwrap());
1018
1019 self.patch_jump(else_idx); self.patch_jump(throw_idx); }
1022
1023 fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
1027 self.scope_mut().begin_scope();
1028 self.compile(slot, node.namespace().unwrap());
1032
1033 let span = self.span_for(&node.namespace().unwrap());
1034
1035 let local_idx = self.scope_mut().declare_phantom(span, true);
1041 let with_idx = self.scope().stack_index(local_idx);
1042
1043 self.scope_mut().push_with();
1044
1045 self.push_op(Op::PushWith, &node.namespace().unwrap());
1046 self.push_uvarint(with_idx.0 as u64);
1047
1048 self.compile(slot, node.body().unwrap());
1049
1050 self.push_op(Op::PopWith, node);
1051 self.scope_mut().pop_with();
1052 self.cleanup_scope(node);
1053 }
1054
1055 fn compile_param_pattern(&mut self, pattern: &ast::Pattern) -> (Formals, CodeIdx) {
1094 let span = self.span_for(pattern);
1095
1096 let (set_idx, pat_bind_name) = match pattern.pat_bind() {
1097 Some(name) => {
1098 let pat_bind_name = name.ident().unwrap().to_string();
1099 (
1100 self.declare_local(&name, pat_bind_name.clone()),
1101 Some(pat_bind_name),
1102 )
1103 }
1104 None => (self.scope_mut().declare_phantom(span, true), None),
1105 };
1106
1107 self.scope_mut().mark_initialised(set_idx);
1109 self.emit_force(pattern);
1110 let throw_idx = self.push_op(Op::JumpIfCatchable, pattern);
1111 self.push_u16(0);
1112
1113 self.push_op(Op::AssertAttrs, pattern);
1115
1116 let ellipsis = pattern.ellipsis_token().is_some();
1117 if !ellipsis {
1118 self.push_op(Op::ValidateClosedFormals, pattern);
1119 }
1120
1121 let mut entries: Vec<TrackedFormal> = vec![];
1125 let mut arguments = BTreeMap::default();
1126
1127 for entry in pattern.pat_entries() {
1128 let ident = entry.ident().unwrap();
1129 let idx = self.declare_local(&ident, ident.to_string());
1130
1131 arguments.insert(ident.into(), entry.default().is_some());
1132
1133 if let Some(default_expr) = entry.default() {
1134 entries.push(TrackedFormal::WithDefault {
1135 local_idx: idx,
1136 finalise_request_idx: {
1140 let span = self.span_for(&default_expr);
1141 self.scope_mut().declare_phantom(span, false)
1142 },
1143 default_expr,
1144 pattern_entry: entry,
1145 });
1146 } else {
1147 entries.push(TrackedFormal::NoDefault {
1148 local_idx: idx,
1149 pattern_entry: entry,
1150 });
1151 }
1152 }
1153
1154 let stack_idx = self.scope().stack_index(set_idx);
1157 for tracked_formal in entries.iter() {
1158 self.push_op(Op::GetLocal, pattern);
1159 self.push_uvarint(stack_idx.0 as u64);
1160 self.emit_literal_ident(&tracked_formal.pattern_entry().ident().unwrap());
1161
1162 let idx = tracked_formal.local_idx();
1163
1164 match tracked_formal {
1167 TrackedFormal::WithDefault {
1168 default_expr,
1169 pattern_entry,
1170 ..
1171 } => {
1172 self.push_op(Op::AttrsTrySelect, &pattern_entry.ident().unwrap());
1189 let jump_to_default = self.push_op(Op::JumpIfNotFound, default_expr);
1190 self.push_u16(0);
1191
1192 self.emit_constant(Value::FinaliseRequest(false), default_expr);
1193
1194 let jump_over_default = self.push_op(Op::Jump, default_expr);
1195 self.push_u16(0);
1196
1197 self.patch_jump(jump_to_default);
1198
1199 self.compile(idx, default_expr.clone());
1201
1202 self.emit_constant(Value::FinaliseRequest(true), default_expr);
1203
1204 self.patch_jump(jump_over_default);
1205 }
1206 TrackedFormal::NoDefault { pattern_entry, .. } => {
1207 self.push_op(Op::AttrsSelect, &pattern_entry.ident().unwrap());
1208 }
1209 }
1210
1211 self.scope_mut().mark_initialised(idx);
1212 if let TrackedFormal::WithDefault {
1213 finalise_request_idx,
1214 ..
1215 } = tracked_formal
1216 {
1217 self.scope_mut().mark_initialised(*finalise_request_idx);
1218 }
1219 }
1220
1221 for tracked_formal in entries.iter() {
1222 if self.scope()[tracked_formal.local_idx()].needs_finaliser {
1223 let stack_idx = self.scope().stack_index(tracked_formal.local_idx());
1224 match tracked_formal {
1225 TrackedFormal::NoDefault { .. } => panic!(
1226 "Snix bug: local for pattern formal needs finaliser, but has no default expr"
1227 ),
1228 TrackedFormal::WithDefault {
1229 finalise_request_idx,
1230 ..
1231 } => {
1232 let finalise_request_stack_idx =
1233 self.scope().stack_index(*finalise_request_idx);
1234
1235 self.push_op(Op::GetLocal, pattern);
1237 self.push_uvarint(finalise_request_stack_idx.0 as u64);
1238 let jump_over_finalise = self.push_op(Op::JumpIfNoFinaliseRequest, pattern);
1239 self.push_u16(0);
1240 self.push_op(Op::Finalise, pattern);
1241 self.push_uvarint(stack_idx.0 as u64);
1242 self.patch_jump(jump_over_finalise);
1243 self.push_op(Op::Pop, pattern);
1245 }
1246 }
1247 }
1248 }
1249
1250 (
1251 (Formals {
1252 arguments,
1253 ellipsis,
1254 span,
1255 name: pat_bind_name,
1256 }),
1257 throw_idx,
1258 )
1259 }
1260
1261 fn compile_lambda(&mut self, slot: LocalIdx, node: &ast::Lambda) -> Option<CodeIdx> {
1262 let formals = match node.param().unwrap() {
1265 ast::Param::Pattern(pat) => Some(self.compile_param_pattern(&pat)),
1266
1267 ast::Param::IdentParam(param) => {
1268 let name = param
1269 .ident()
1270 .unwrap()
1271 .ident_token()
1272 .unwrap()
1273 .text()
1274 .to_string();
1275
1276 let idx = self.declare_local(¶m, &name);
1277 self.scope_mut().mark_initialised(idx);
1278
1279 self.context_mut().lambda.param_name = name;
1281
1282 None
1283 }
1284 };
1285
1286 self.compile(slot, node.body().unwrap());
1287 if let Some((formals, throw_idx)) = formals {
1288 self.context_mut().lambda.formals = Some(formals);
1289 self.context_mut().lambda.param_name = String::new();
1291 Some(throw_idx)
1292 } else {
1293 self.context_mut().lambda.formals = None;
1294 None
1295 }
1296 }
1297
1298 fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
1299 where
1300 N: ToSpan,
1301 F: FnOnce(&mut Compiler, LocalIdx),
1302 {
1303 self.compile_lambda_or_thunk(true, outer_slot, node, |comp, idx| {
1304 content(comp, idx);
1305 None
1306 })
1307 }
1308
1309 fn compile_lambda_or_thunk<N, F>(
1311 &mut self,
1312 is_suspended_thunk: bool,
1313 outer_slot: LocalIdx,
1314 node: &N,
1315 content: F,
1316 ) where
1317 N: ToSpan,
1318 F: FnOnce(&mut Compiler, LocalIdx) -> Option<CodeIdx>,
1319 {
1320 let name = self.scope()[outer_slot].name();
1321 self.new_context();
1322
1323 self.context_mut().lambda.name = name;
1326
1327 let span = self.span_for(node);
1328 let slot = self.scope_mut().declare_phantom(span, false);
1329 self.scope_mut().begin_scope();
1330
1331 let throw_idx = content(self, slot);
1332 self.cleanup_scope(node);
1333 if let Some(throw_idx) = throw_idx {
1334 self.patch_jump(throw_idx);
1335 }
1336
1337 let mut compiled = self.contexts.pop().unwrap();
1340
1341 compiled
1343 .lambda
1344 .chunk
1345 .push_op(Op::Return, self.span_for(node));
1346
1347 let lambda = Rc::new(compiled.lambda);
1348 if is_suspended_thunk {
1349 self.observer.observe_compiled_thunk(&lambda);
1350 } else {
1351 self.observer.observe_compiled_lambda(&lambda);
1352 }
1353
1354 if lambda.upvalue_count == 0 && !compiled.captures_with_stack {
1356 self.emit_constant(
1357 if is_suspended_thunk {
1358 Value::Thunk(Thunk::new_suspended(lambda, span))
1359 } else {
1360 Value::Closure(Rc::new(Closure::new(lambda)))
1361 },
1362 node,
1363 );
1364 return;
1365 }
1366
1367 let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
1372
1373 let code_idx = self.push_op(
1374 if is_suspended_thunk {
1375 Op::ThunkSuspended
1376 } else {
1377 Op::ThunkClosure
1378 },
1379 node,
1380 );
1381 self.push_uvarint(blueprint_idx.0 as u64);
1382
1383 self.emit_upvalue_data(
1384 outer_slot,
1385 node,
1386 compiled.scope.upvalues,
1387 compiled.captures_with_stack,
1388 );
1389
1390 if !is_suspended_thunk && !self.scope()[outer_slot].needs_finaliser {
1391 if !self.scope()[outer_slot].must_thunk {
1392 self.chunk().code[code_idx.0] = Op::Closure as u8;
1396 } else {
1397 #[cfg(debug_assertions)]
1403 {
1404 self.push_op(Op::Finalise, &self.span_for(node));
1405 self.push_uvarint(self.scope().stack_index(outer_slot).0 as u64);
1406 }
1407 }
1408 }
1409 }
1410
1411 fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
1412 self.compile(slot, node.argument().unwrap());
1417 self.compile(slot, node.lambda().unwrap());
1418 self.emit_force(&node.lambda().unwrap());
1419 self.push_op(Op::Call, node);
1420 }
1421
1422 fn emit_upvalue_data<T: ToSpan>(
1425 &mut self,
1426 slot: LocalIdx,
1427 _: &T, upvalues: Vec<Upvalue>,
1429 capture_with: bool,
1430 ) {
1431 let data = UpvalueData::new(upvalues.len(), capture_with);
1434 self.push_uvarint(data.into_raw());
1435
1436 for upvalue in upvalues {
1437 match upvalue.kind {
1438 UpvalueKind::Local(idx) => {
1439 let target = &self.scope()[idx];
1440 let stack_idx = self.scope().stack_index(idx);
1441
1442 if !target.initialised {
1445 self.push_uvarint(Position::deferred_local(stack_idx).0);
1446 self.scope_mut().mark_needs_finaliser(slot);
1447 } else {
1448 if slot == idx {
1450 self.scope_mut().mark_must_thunk(slot);
1451 }
1452 self.push_uvarint(Position::stack_index(stack_idx).0);
1453 }
1454 }
1455
1456 UpvalueKind::Upvalue(idx) => {
1457 self.push_uvarint(Position::upvalue_index(idx).0);
1458 }
1459 };
1460 }
1461 }
1462
1463 pub fn compile_cur_pos(&mut self, node: &ast::CurPos) {
1464 let value = match self.file.name() {
1465 crate::REPL_LOCATION => Value::Null,
1466 _ => {
1467 let span = self.span_for(node);
1468 let pos = self.file.find_line_col(span.low());
1469 let abs_path = std::fs::canonicalize(self.file.name())
1470 .unwrap()
1471 .to_string_lossy()
1472 .to_string();
1473 let attrs = NixAttrs::from_iter([
1474 ("line", Value::Integer((pos.line + 1) as i64)),
1475 ("column", Value::Integer((pos.column + 1) as i64)),
1476 ("file", Value::String(NixString::from(abs_path))),
1477 ]);
1478 Value::Attrs(attrs)
1479 }
1480 };
1481
1482 self.emit_constant(value, node);
1483 }
1484
1485 fn emit_literal_ident(&mut self, ident: &ast::Ident) {
1489 self.emit_constant(Value::String(ident.clone().into()), ident);
1490 }
1491
1492 fn patch_jump(&mut self, idx: CodeIdx) {
1499 self.chunk().patch_jump(idx.0);
1500 }
1501
1502 fn cleanup_scope<N: ToSpan>(&mut self, node: &N) {
1505 let (popcount, unused_spans) = self.scope_mut().end_scope();
1509
1510 for span in &unused_spans {
1511 self.emit_warning(span, WarningKind::UnusedBinding);
1512 }
1513
1514 if popcount > 0 {
1515 self.push_op(Op::CloseScope, node);
1516 self.push_uvarint(popcount as u64);
1517 }
1518 }
1519
1520 fn new_context(&mut self) {
1523 self.contexts.push(self.context().inherit());
1524 }
1525
1526 fn declare_local<S: Into<String>, N: ToSpan>(&mut self, node: &N, name: S) -> LocalIdx {
1530 let name = name.into();
1531 let depth = self.scope().scope_depth();
1532
1533 if let Some((global_ident, _)) = self.globals.get_key_value(name.as_str()) {
1536 self.emit_warning(node, WarningKind::ShadowedGlobal(global_ident));
1537 }
1538
1539 let span = self.span_for(node);
1540 let (idx, shadowed) = self.scope_mut().declare_local(name, span);
1541
1542 if let Some(shadow_idx) = shadowed {
1543 let other = &self.scope()[shadow_idx];
1544 if other.depth == depth {
1545 self.emit_error(node, ErrorKind::VariableAlreadyDefined(other.span));
1546 }
1547 }
1548
1549 idx
1550 }
1551
1552 fn has_dynamic_ancestor(&mut self) -> bool {
1557 let mut ancestor_has_with = false;
1558
1559 for ctx in self.contexts.iter_mut() {
1560 if ancestor_has_with {
1561 ctx.captures_with_stack = true;
1564 } else {
1565 ancestor_has_with = ctx.scope.has_with();
1567 }
1568 }
1569
1570 ancestor_has_with
1571 }
1572
1573 fn emit_force<N: ToSpan>(&mut self, node: &N) {
1574 self.push_op(Op::Force, node);
1575 }
1576
1577 fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind) {
1578 let span = self.span_for(node);
1579 self.warnings.push(EvalWarning { kind, span })
1580 }
1581
1582 fn emit_error<N: ToSpan>(&mut self, node: &N, kind: ErrorKind) {
1583 let span = self.span_for(node);
1584 self.errors
1585 .push(Error::new(kind, span, self.source.clone()))
1586 }
1587}
1588
1589fn expr_static_str(node: &ast::Str) -> Option<SmolStr> {
1591 let mut parts = node.normalized_parts();
1592
1593 if parts.len() != 1 {
1594 return None;
1595 }
1596
1597 if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
1598 return Some(SmolStr::new(lit));
1599 }
1600
1601 None
1602}
1603
1604fn expr_static_attr_str(node: &ast::Attr) -> Option<SmolStr> {
1607 match node {
1608 ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
1609 ast::Attr::Str(s) => expr_static_str(s),
1610
1611 ast::Attr::Dynamic(dynamic) => match dynamic.expr().unwrap() {
1616 ast::Expr::Str(s) => expr_static_str(&s),
1617 _ => None,
1618 },
1619 }
1620}
1621
1622fn is_interpolated_path(parts: &[InterpolPart<PathContent>]) -> bool {
1624 parts
1625 .iter()
1626 .any(|part| matches!(part, ast::InterpolPart::Interpolation(_)))
1627}
1628
1629fn compile_src_builtin(
1636 name: &'static str,
1637 code: &str,
1638 source: SourceCode,
1639 weak: &Weak<GlobalsMap>,
1640) -> Value {
1641 use std::fmt::Write;
1642
1643 let parsed = rnix::ast::Root::parse(code);
1644
1645 if !parsed.errors().is_empty() {
1646 let mut out = format!("BUG: code for source-builtin '{name}' had parser errors");
1647 for error in parsed.errors() {
1648 writeln!(out, "{error}").unwrap();
1649 }
1650
1651 panic!("{}", out);
1652 }
1653
1654 let file = source.add_file(format!("<src-builtins/{name}.nix>"), code.to_string());
1655 let weak = weak.clone();
1656
1657 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1658 let result = compile(
1659 &parsed.tree().expr().unwrap(),
1660 None,
1661 weak.upgrade().unwrap(),
1662 None,
1663 &source,
1664 &file,
1665 Default::default(),
1666 )
1667 .map_err(|e| ErrorKind::NativeError {
1668 gen_type: "derivation",
1669 err: Box::new(e),
1670 })?;
1671
1672 if !result.errors.is_empty() {
1673 return Err(ErrorKind::ImportCompilerError {
1674 path: format!("src-builtins/{name}.nix").into(),
1675 errors: result.errors,
1676 });
1677 }
1678
1679 Ok(Value::Thunk(Thunk::new_suspended(result.lambda, file.span)))
1680 })))
1681}
1682
1683pub fn prepare_globals(
1692 builtins: Vec<(&'static str, Value)>,
1693 src_builtins: Vec<(&'static str, &'static str)>,
1694 source: SourceCode,
1695 enable_import: bool,
1696) -> Rc<GlobalsMap> {
1697 Rc::new_cyclic(Box::new(move |weak: &Weak<GlobalsMap>| {
1698 let mut builtins: GlobalsMap = FxHashMap::from_iter(builtins);
1701
1702 if enable_import {
1707 let import = Value::Builtin(import::builtins_import(weak, source.clone()));
1708 builtins.insert("import", import);
1709 }
1710
1711 let mut globals: GlobalsMap = FxHashMap::default();
1714
1715 let weak_globals = weak.clone();
1719 builtins.insert(
1720 "builtins",
1721 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1722 Ok(weak_globals
1723 .upgrade()
1724 .unwrap()
1725 .get("builtins")
1726 .cloned()
1727 .unwrap())
1728 }))),
1729 );
1730
1731 globals.insert("true", Value::Bool(true));
1733 globals.insert("false", Value::Bool(false));
1734 globals.insert("null", Value::Null);
1735
1736 builtins.extend(src_builtins.into_iter().map(move |(name, code)| {
1739 let compiled = compile_src_builtin(name, code, source.clone(), weak);
1740 (name, compiled)
1741 }));
1742
1743 globals.insert(
1746 "builtins",
1747 Value::attrs(NixAttrs::from_iter(builtins.clone())),
1748 );
1749
1750 for global in GLOBAL_BUILTINS {
1753 if let Some(builtin) = builtins.get(global).cloned() {
1754 globals.insert(global, builtin);
1755 }
1756 }
1757
1758 globals
1759 }))
1760}
1761
1762pub fn compile(
1763 expr: &ast::Expr,
1764 location: Option<PathBuf>,
1765 globals: Rc<GlobalsMap>,
1766 env: Option<&FxHashMap<SmolStr, Value>>,
1767 source: &SourceCode,
1768 file: &codemap::File,
1769 observer: OptionalCompilerObserver<'_>,
1770) -> EvalResult<CompilationOutput> {
1771 let mut c = Compiler::new(location, globals.clone(), env, source, file, observer)?;
1772
1773 let root_span = c.span_for(expr);
1774 let root_slot = c.scope_mut().declare_phantom(root_span, false);
1775 c.compile(root_slot, expr.clone());
1776
1777 c.emit_force(expr);
1782 if let Some(env) = env
1783 && !env.is_empty()
1784 {
1785 c.push_op(Op::CloseScope, &root_span);
1786 c.push_uvarint(env.len() as u64);
1787 }
1788 c.push_op(Op::Return, &root_span);
1789
1790 let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
1791 c.observer.observe_compiled_toplevel(&lambda);
1792
1793 Ok(CompilationOutput {
1794 lambda,
1795 warnings: c.warnings,
1796 errors: c.errors,
1797 })
1798}