1use std::cell::RefCell;
8use std::rc::Rc;
9
10use rnix::ast::{self, AstToken, HasEntry, InterpolPart};
11use rowan::ast::AstNode;
12
13use crate::chunk::Chunk;
14use crate::error::CompileError;
15use crate::intern::Interner;
16use crate::opcode::OpCode;
17use crate::value::{VMClosure, VMValue};
18
19#[derive(Debug, Clone)]
21struct Local {
22 name: String,
24 depth: u32,
26 is_captured: bool,
28 slot: u16,
33}
34
35#[derive(Debug, Clone, Copy)]
37struct UpvalueDesc {
38 is_local: bool,
41 index: u16,
43}
44
45enum LetBinding {
47 Value(ast::Expr),
49 Inherit,
51 InheritFrom(ast::Expr, String),
53}
54
55enum RecAttrBinding {
57 Value(ast::Expr),
59 Inherit,
61 InheritFrom(ast::Expr, String),
63 Dotted(Vec<(Vec<String>, ast::Expr)>),
65}
66
67pub struct Compiler {
76 chunk: Chunk,
78 locals: Vec<Local>,
80 upvalues: Vec<UpvalueDesc>,
82 scope_depth: u32,
84 current_line: u32,
86 interner: Rc<RefCell<Interner>>,
88 enclosing: Option<*mut Compiler>,
90 with_depth: u32,
92 base_dir: Option<std::path::PathBuf>,
94 stack_depth: u16,
100 source_text: Option<Rc<String>>,
103 tail_position: bool,
108 with_scope_locals: Vec<u16>,
114}
115
116impl Compiler {
117 fn new() -> Self {
119 Self {
120 chunk: Chunk::new(),
121 locals: Vec::new(),
122 upvalues: Vec::new(),
123 scope_depth: 0,
124 current_line: 0,
125 interner: Rc::new(RefCell::new(Interner::new())),
126 enclosing: None,
127 with_depth: 0,
128 base_dir: None,
129 stack_depth: 0,
130 source_text: None,
131 tail_position: false,
132 with_scope_locals: Vec::new(),
133 }
134 }
135
136 fn with_interner(interner: Rc<RefCell<Interner>>) -> Self {
138 Self {
139 chunk: Chunk::new(),
140 locals: Vec::new(),
141 upvalues: Vec::new(),
142 scope_depth: 0,
143 current_line: 0,
144 interner,
145 enclosing: None,
146 with_depth: 0,
147 base_dir: None,
148 stack_depth: 0,
149 source_text: None,
150 tail_position: false,
151 with_scope_locals: Vec::new(),
152 }
153 }
154
155 pub fn compile_with_base_dir(
158 input: &str,
159 base_dir: std::path::PathBuf,
160 ) -> Result<(Chunk, Interner), CompileError> {
161 let parse = rnix::Root::parse(input);
162 if !parse.errors().is_empty() {
163 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
164 return Err(CompileError::ParseError(msgs.join("; ")));
165 }
166 let root = parse.tree();
167 let expr = root
168 .expr()
169 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
170 let mut compiler = Self::new();
171 compiler.base_dir = Some(base_dir);
172 compiler.compile_expr(&expr)?;
173 compiler.emit(OpCode::Return);
174 let interner = match Rc::try_unwrap(compiler.interner) {
175 Ok(cell) => cell.into_inner(),
176 Err(rc) => (*rc).borrow().clone(),
177 };
178 Ok((compiler.chunk, interner))
179 }
180
181 pub fn compile_with_shared_interner(
185 input: &str,
186 base_dir: std::path::PathBuf,
187 interner: Rc<RefCell<Interner>>,
188 ) -> Result<Chunk, CompileError> {
189 let parse = rnix::Root::parse(input);
190 if !parse.errors().is_empty() {
191 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
192 return Err(CompileError::ParseError(msgs.join("; ")));
193 }
194 let root = parse.tree();
195 let expr = root
196 .expr()
197 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
198 let mut compiler = Self::with_interner(interner);
199 compiler.base_dir = Some(base_dir);
200 compiler.source_text = Some(Rc::new(input.to_string()));
201 compiler.compile_expr(&expr)?;
202 compiler.emit(OpCode::Return);
203 Ok(compiler.chunk)
204 }
205
206 pub fn compile_expression(
209 input: &str,
210 base_dir: &std::path::Path,
211 interner: Rc<RefCell<Interner>>,
212 ) -> Result<Chunk, CompileError> {
213 let parse = rnix::Root::parse(input);
214 if !parse.errors().is_empty() {
215 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
216 return Err(CompileError::ParseError(msgs.join("; ")));
217 }
218 let root = parse.tree();
219 let expr = root
220 .expr()
221 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
222 let mut compiler = Self::with_interner(interner);
223 compiler.base_dir = Some(base_dir.to_path_buf());
224 compiler.compile_expr(&expr)?;
225 compiler.emit(OpCode::Return);
226 Ok(compiler.chunk)
227 }
228
229 pub fn compile(input: &str) -> Result<(Chunk, Interner), CompileError> {
231 let parse = rnix::Root::parse(input);
232 if !parse.errors().is_empty() {
233 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
234 return Err(CompileError::ParseError(msgs.join("; ")));
235 }
236 let root = parse.tree();
237 let expr = root
238 .expr()
239 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
240 let mut compiler = Self::new();
241 compiler.compile_expr(&expr)?;
242 compiler.emit(OpCode::Return);
243 let interner = match Rc::try_unwrap(compiler.interner) {
244 Ok(cell) => cell.into_inner(),
245 Err(rc) => (*rc).borrow().clone(),
246 };
247 Ok((compiler.chunk, interner))
248 }
249
250 fn try_eval_const(expr: &ast::Expr) -> Option<VMValue> {
256 match expr {
257 ast::Expr::Literal(lit) => Self::try_eval_literal(lit),
258 ast::Expr::Paren(p) => Self::try_eval_const(&p.expr()?),
259 ast::Expr::UnaryOp(op) => Self::try_fold_unary(op),
260 ast::Expr::BinOp(binop) => Self::try_fold_binop(binop),
261 ast::Expr::IfElse(ie) => Self::try_fold_if(ie),
262 ast::Expr::Ident(id) => {
263 let name = ident_text(id);
264 match name.as_str() {
265 "true" => Some(VMValue::Bool(true)),
266 "false" => Some(VMValue::Bool(false)),
267 "null" => Some(VMValue::Null),
268 _ => None,
269 }
270 }
271 _ => None,
272 }
273 }
274
275 fn try_eval_literal(lit: &ast::Literal) -> Option<VMValue> {
277 match lit.kind() {
278 ast::LiteralKind::Integer(tok) => {
279 Some(VMValue::Int(tok.value().ok()?))
280 }
281 ast::LiteralKind::Float(tok) => {
282 Some(VMValue::Float(tok.value().ok()?))
283 }
284 ast::LiteralKind::Uri(_) => None,
285 }
286 }
287
288 fn try_fold_unary(op: &ast::UnaryOp) -> Option<VMValue> {
290 let inner = Self::try_eval_const(&op.expr()?)?;
291 let kind = op.operator()?;
292 match kind {
293 ast::UnaryOpKind::Negate => match inner {
294 VMValue::Int(n) => Some(VMValue::Int(-n)),
295 VMValue::Float(f) => Some(VMValue::Float(-f)),
296 _ => None,
297 },
298 ast::UnaryOpKind::Invert => match inner {
299 VMValue::Bool(b) => Some(VMValue::Bool(!b)),
300 _ => None,
301 },
302 }
303 }
304
305 fn try_fold_binop(binop: &ast::BinOp) -> Option<VMValue> {
307 let lhs = Self::try_eval_const(&binop.lhs()?)?;
308 let rhs = Self::try_eval_const(&binop.rhs()?)?;
309 let op = binop.operator()?;
310
311 match op {
312 ast::BinOpKind::Add => match (&lhs, &rhs) {
313 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a + b)),
314 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a + b)),
315 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 + b)),
316 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a + *b as f64)),
317 (VMValue::String(a), VMValue::String(b)) => {
318 Some(VMValue::String(format!("{a}{b}")))
319 }
320 _ => None,
321 },
322 ast::BinOpKind::Sub => match (&lhs, &rhs) {
323 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a - b)),
324 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a - b)),
325 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 - b)),
326 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a - *b as f64)),
327 _ => None,
328 },
329 ast::BinOpKind::Mul => match (&lhs, &rhs) {
330 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a * b)),
331 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a * b)),
332 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 * b)),
333 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a * *b as f64)),
334 _ => None,
335 },
336 ast::BinOpKind::Div => match (&lhs, &rhs) {
337 (VMValue::Int(_), VMValue::Int(0)) => None, (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a / b)),
339 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a / b)),
340 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 / b)),
341 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a / *b as f64)),
342 _ => None,
343 },
344 ast::BinOpKind::Equal => Some(VMValue::Bool(Self::const_eq(&lhs, &rhs))),
345 ast::BinOpKind::NotEqual => Some(VMValue::Bool(!Self::const_eq(&lhs, &rhs))),
346 ast::BinOpKind::Less => Self::const_cmp(&lhs, &rhs)
347 .map(|o| VMValue::Bool(o == std::cmp::Ordering::Less)),
348 ast::BinOpKind::LessOrEq => Self::const_cmp(&lhs, &rhs)
349 .map(|o| VMValue::Bool(o != std::cmp::Ordering::Greater)),
350 ast::BinOpKind::More => Self::const_cmp(&lhs, &rhs)
351 .map(|o| VMValue::Bool(o == std::cmp::Ordering::Greater)),
352 ast::BinOpKind::MoreOrEq => Self::const_cmp(&lhs, &rhs)
353 .map(|o| VMValue::Bool(o != std::cmp::Ordering::Less)),
354 ast::BinOpKind::And => match (&lhs, &rhs) {
355 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(*a && *b)),
356 _ => None,
357 },
358 ast::BinOpKind::Or => match (&lhs, &rhs) {
359 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(*a || *b)),
360 _ => None,
361 },
362 ast::BinOpKind::Implication => match (&lhs, &rhs) {
363 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(!a || *b)),
364 _ => None,
365 },
366 _ => None,
367 }
368 }
369
370 fn try_fold_if(ie: &ast::IfElse) -> Option<VMValue> {
372 let cond = Self::try_eval_const(&ie.condition()?)?;
373 match cond {
374 VMValue::Bool(true) => Self::try_eval_const(&ie.body()?),
375 VMValue::Bool(false) => Self::try_eval_const(&ie.else_body()?),
376 _ => None,
377 }
378 }
379
380 fn const_eq(a: &VMValue, b: &VMValue) -> bool {
382 match (a, b) {
383 (VMValue::Null, VMValue::Null) => true,
384 (VMValue::Bool(a), VMValue::Bool(b)) => a == b,
385 (VMValue::Int(a), VMValue::Int(b)) => a == b,
386 (VMValue::Float(a), VMValue::Float(b)) => a == b,
387 (VMValue::Int(a), VMValue::Float(b)) | (VMValue::Float(b), VMValue::Int(a)) => {
388 (*a as f64) == *b
389 }
390 (VMValue::String(a), VMValue::String(b)) => a == b,
391 _ => false,
392 }
393 }
394
395 fn const_cmp(a: &VMValue, b: &VMValue) -> Option<std::cmp::Ordering> {
397 match (a, b) {
398 (VMValue::Int(a), VMValue::Int(b)) => Some(a.cmp(b)),
399 (VMValue::Float(a), VMValue::Float(b)) => a.partial_cmp(b),
400 (VMValue::Int(a), VMValue::Float(b)) => (*a as f64).partial_cmp(b),
401 (VMValue::Float(a), VMValue::Int(b)) => a.partial_cmp(&(*b as f64)),
402 (VMValue::String(a), VMValue::String(b)) => Some(a.cmp(b)),
403 _ => None,
404 }
405 }
406
407 fn compile_expr(&mut self, expr: &ast::Expr) -> Result<(), CompileError> {
410 self.current_line = line_of(expr);
411
412 if let Some(folded) = Self::try_eval_const(expr) {
415 return self.emit_constant(folded);
416 }
417
418 let tail = self.tail_position;
423 self.tail_position = false;
424
425 match expr {
426 ast::Expr::Literal(lit) => self.compile_literal(lit),
427 ast::Expr::Str(s) => self.compile_str(s),
428 ast::Expr::Ident(id) => self.compile_ident(id),
429 ast::Expr::LetIn(letin) => self.compile_let(letin),
430 ast::Expr::AttrSet(set) => self.compile_attrset(set),
431 ast::Expr::Select(sel) => self.compile_select(sel),
432 ast::Expr::HasAttr(ha) => self.compile_has_attr(ha),
433 ast::Expr::IfElse(ie) => {
434 self.tail_position = tail;
435 self.compile_if(ie)
436 }
437 ast::Expr::Lambda(lam) => self.compile_lambda(lam),
438 ast::Expr::Apply(app) => {
439 self.tail_position = tail;
440 self.compile_apply(app)
441 }
442 ast::Expr::BinOp(op) => self.compile_binop(op),
443 ast::Expr::UnaryOp(op) => self.compile_unary(op),
444 ast::Expr::With(w) => self.compile_with(w),
445 ast::Expr::Assert(a) => {
446 self.tail_position = tail;
447 self.compile_assert(a)
448 }
449 ast::Expr::List(l) => self.compile_list(l),
450 ast::Expr::Paren(p) => {
451 self.tail_position = tail;
452 let inner = p
453 .expr()
454 .ok_or_else(|| CompileError::MissingNode("paren expr".to_string()))?;
455 self.compile_expr(&inner)
456 }
457 ast::Expr::Root(r) => {
458 self.tail_position = tail;
459 let inner = r
460 .expr()
461 .ok_or_else(|| CompileError::MissingNode("root expr".to_string()))?;
462 self.compile_expr(&inner)
463 }
464 ast::Expr::PathAbs(p) => {
465 let text = p.syntax().text().to_string();
466 self.emit_constant(VMValue::Path(text))
467 }
468 ast::Expr::PathRel(p) => {
469 let text = p.syntax().text().to_string();
470 let resolved = self.resolve_relative_path(&text);
473 self.emit_constant(VMValue::Path(resolved))
474 }
475 ast::Expr::PathHome(p) => {
476 let text = p.syntax().text().to_string();
477 self.emit_constant(VMValue::Path(text))
478 }
479 ast::Expr::PathSearch(p) => {
480 let text = p.syntax().text().to_string();
481 let inner = text
482 .strip_prefix('<')
483 .and_then(|s| s.strip_suffix('>'))
484 .unwrap_or(&text);
485 if let Some(resolved) = resolve_search_path(inner) {
486 self.emit_constant(VMValue::Path(resolved))
487 } else {
488 let msg = format!("search path '{text}' not in NIX_PATH");
492 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
493 tc.scope_depth = 1;
494 tc.base_dir = self.base_dir.clone();
495 tc.emit_constant(VMValue::String(msg))?;
496 tc.emit(OpCode::Throw);
497 tc.emit(OpCode::Return);
498 let closure = VMValue::Closure(VMClosure {
499 chunk: Rc::new(tc.chunk),
500 upvalues: Vec::new(),
501 arity: 0,
502 name: None,
503 formals: Vec::new(),
504 });
505 let idx = self.chunk.add_constant(closure)?;
506 self.emit(OpCode::MakeThunk);
507 self.stack_depth += 1;
508 self.emit_u16(idx);
509 self.emit_u16(0); Ok(())
511 }
512 }
513 ast::Expr::LegacyLet(ll) => {
514 self.compile_legacy_let(&ll)
518 }
519 ast::Expr::CurPos(_) => {
520 self.emit_constant(VMValue::Null)
522 }
523 other => Err(CompileError::Unsupported(format!("{other:?}"))),
524 }
525 }
526
527 fn compile_literal(&mut self, lit: &ast::Literal) -> Result<(), CompileError> {
530 match lit.kind() {
531 ast::LiteralKind::Integer(tok) => {
532 let n = tok.value().map_err(|e| {
533 CompileError::ParseError(format!("invalid integer: {e}"))
534 })?;
535 self.emit_constant(VMValue::Int(n))
536 }
537 ast::LiteralKind::Float(tok) => {
538 let f = tok.value().map_err(|e| {
539 CompileError::ParseError(format!("invalid float: {e}"))
540 })?;
541 self.emit_constant(VMValue::Float(f))
542 }
543 ast::LiteralKind::Uri(tok) => {
544 let s = tok.syntax().text().to_string();
545 self.emit_constant(VMValue::String(s))
546 }
547 }
548 }
549
550 fn compile_str(&mut self, s: &ast::Str) -> Result<(), CompileError> {
553 let parts: Vec<_> = s.normalized_parts().into_iter().collect();
554
555 if parts.len() == 1 {
557 if let InterpolPart::Literal(text) = &parts[0] {
558 return self.emit_constant(VMValue::String(String::from(text.as_str())));
559 }
560 }
561
562 let mut count: u16 = 0;
564 for part in &parts {
565 match part {
566 InterpolPart::Literal(text) => {
567 self.emit_constant(VMValue::String(text.to_string()))?;
568 count += 1;
569 }
570 InterpolPart::Interpolation(interp) => {
571 let expr = interp
572 .expr()
573 .ok_or_else(|| CompileError::MissingNode("interpolation expr".to_string()))?;
574 self.compile_expr(&expr)?;
575 count += 1;
576 }
577 }
578 }
579
580 if count == 0 {
581 self.emit_constant(VMValue::String(String::new()))
583 } else if count == 1 {
584 Ok(())
586 } else {
587 self.emit(OpCode::Interpolate);
588 self.emit_u16(count);
589 self.stack_depth = self.stack_depth.saturating_sub(count) + 1;
591 Ok(())
592 }
593 }
594
595 fn compile_ident(&mut self, ident: &ast::Ident) -> Result<(), CompileError> {
598 let name = ident_text(ident);
599 match name.as_str() {
600 "true" => {
601 self.emit(OpCode::True);
602 Ok(())
603 }
604 "false" => {
605 self.emit(OpCode::False);
606 Ok(())
607 }
608 "null" => {
609 self.emit(OpCode::Null);
610 Ok(())
611 }
612 _ => {
613 if let Some(idx) = self.resolve_local(&name) {
615 self.emit(OpCode::GetLocal);
616 self.emit_u16(self.local_stack_slot(idx));
617 return Ok(());
618 }
619 if let Some(idx) = self.resolve_upvalue(&name) {
621 self.emit(OpCode::GetUpvalue);
622 self.emit_u16(idx as u16);
623 return Ok(());
624 }
625 if name == "builtins" {
627 self.emit(OpCode::PushBuiltins);
628 return Ok(());
629 }
630 if is_global_builtin(&name) {
633 self.emit(OpCode::PushBuiltins);
634 let key_idx = self.add_attr_key(name)?;
635 self.emit(OpCode::GetAttr);
636 self.emit_u16(key_idx);
637 return Ok(());
638 }
639 if self.has_with_scope() {
641 let name_idx = self.chunk.add_constant(VMValue::String(name))?;
642 self.emit(OpCode::LookupWith);
643 self.emit_u16(name_idx);
644 return Ok(());
645 }
646 Err(CompileError::Unsupported(format!(
647 "unresolved variable: {name}"
648 )))
649 }
650 }
651 }
652
653 fn compile_let(&mut self, letin: &ast::LetIn) -> Result<(), CompileError> {
656 self.begin_scope();
657
658 let mut bindings: Vec<(String, LetBinding)> = Vec::new();
662
663 for entry in letin.entries() {
664 match entry {
665 ast::Entry::AttrpathValue(ref apv) => {
666 let attrpath = apv.attrpath().ok_or_else(|| {
667 CompileError::MissingNode("binding attrpath".to_string())
668 })?;
669 let keys: Vec<_> = attrpath.attrs().collect();
670 if keys.len() != 1 {
671 return Err(CompileError::Unsupported(
672 "dotted let bindings".to_string(),
673 ));
674 }
675 let key = static_attr_name(&keys[0])?;
676 let value_expr = apv.value().ok_or_else(|| {
677 CompileError::MissingNode("binding value".to_string())
678 })?;
679 bindings.push((key, LetBinding::Value(value_expr)));
680 }
681 ast::Entry::Inherit(ref inherit) => {
682 if let Some(from) = inherit.from() {
683 let source_expr = from.expr().ok_or_else(|| {
684 CompileError::MissingNode("inherit from expr".to_string())
685 })?;
686 for attr in inherit.attrs() {
687 let name = static_attr_name(&attr)?;
688 bindings.push((name.clone(), LetBinding::InheritFrom(source_expr.clone(), name)));
689 }
690 } else {
691 for attr in inherit.attrs() {
692 let name = static_attr_name(&attr)?;
693 bindings.push((name, LetBinding::Inherit));
694 }
695 }
696 }
697 }
698 }
699
700 {
702 let pairs: Vec<(String, &ast::Expr)> = bindings
703 .iter()
704 .filter_map(|(name, binding)| match binding {
705 LetBinding::Value(expr) => Some((name.clone(), expr as &ast::Expr)),
706 _ => None,
707 })
708 .collect();
709 for warning in detect_trivial_cycles(&pairs) {
710 eprintln!("{warning}");
711 }
712 }
713
714 let binding_count = u16::try_from(bindings.len())
715 .map_err(|_| CompileError::TooManyLocals)?;
716
717 for (name, _) in &bindings {
719 self.emit(OpCode::Null); self.add_local(name.clone())?;
721 }
722
723 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
728
729 for (name, binding) in &bindings {
730 let local_idx = self.resolve_local(name).unwrap();
731 let slot = self.locals[local_idx as usize].slot;
732 match binding {
733 LetBinding::Value(expr) => {
734 if Self::is_trivial_value_for_rec(expr) {
739 self.compile_expr(expr)?;
740 } else {
741 let uv_descs = self.compile_thunk_deferred(expr)?;
742 if !uv_descs.is_empty() {
743 thunk_slots.push((slot, uv_descs));
744 }
745 }
746 self.emit(OpCode::SetLocal);
747 self.emit_u16(slot);
748 self.emit(OpCode::Pop);
749 }
750 LetBinding::Inherit => {
751 let saved_depth = self.locals[local_idx as usize].depth;
753 self.locals[local_idx as usize].depth = u32::MAX;
754 if let Some(outer_idx) = self.resolve_local(name) {
755 self.emit(OpCode::GetLocal);
756 self.emit_u16(self.local_stack_slot(outer_idx));
757 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
758 self.emit(OpCode::GetUpvalue);
759 self.emit_u16(uv_idx as u16);
760 } else if self.has_with_scope() {
761 let name_idx = self.chunk.add_constant(VMValue::String(name.clone()))?;
762 self.emit(OpCode::LookupWith);
763 self.emit_u16(name_idx);
764 } else {
765 self.locals[local_idx as usize].depth = saved_depth;
766 return Err(CompileError::Unsupported(format!(
767 "inherit: cannot resolve '{name}' in enclosing scope"
768 )));
769 }
770 self.locals[local_idx as usize].depth = saved_depth;
771 self.emit(OpCode::SetLocal);
772 self.emit_u16(slot);
773 self.emit(OpCode::Pop);
774 }
775 LetBinding::InheritFrom(source_expr, attr_name) => {
776 let uv_descs = self.compile_inherit_from_thunk_deferred(source_expr, attr_name)?;
780 if !uv_descs.is_empty() {
781 thunk_slots.push((slot, uv_descs));
782 }
783 self.emit(OpCode::SetLocal);
784 self.emit_u16(slot);
785 self.emit(OpCode::Pop);
786 }
787 }
788 }
789
790 for (slot, uv_descs) in &thunk_slots {
792 self.emit(OpCode::PatchThunkUpvalues);
793 self.emit_u16(*slot);
794 self.emit_u16(uv_descs.len() as u16);
795 for uv in uv_descs {
796 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
797 self.emit_u16(uv.index);
798 }
799 }
800
801 let body = letin
804 .body()
805 .ok_or_else(|| CompileError::MissingNode("let body".to_string()))?;
806 self.compile_expr(&body)?;
807
808 self.end_scope(binding_count);
810
811 Ok(())
812 }
813
814 fn is_trivial_value(expr: &ast::Expr) -> bool {
816 match expr {
817 ast::Expr::Literal(_) => true,
818 ast::Expr::Str(s) => {
819 for part in s.normalized_parts() {
820 if !matches!(part, InterpolPart::Literal(_)) {
821 return false;
822 }
823 }
824 true
825 }
826 ast::Expr::Ident(id) => {
827 let name = ident_text(id);
828 matches!(name.as_str(), "true" | "false" | "null")
829 }
830 ast::Expr::Lambda(_) => true,
831 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_value(&inner)),
832 ast::Expr::List(list) => list.items().next().is_none(),
833 ast::Expr::AttrSet(set) => set.rec_token().is_none() && set.entries().next().is_none(),
834 _ => false,
835 }
836 }
837
838 fn is_trivial_value_for_rec(expr: &ast::Expr) -> bool {
847 match expr {
848 ast::Expr::Lambda(_) => false,
850 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_value_for_rec(&inner)),
851 _ => Self::is_trivial_value(expr),
852 }
853 }
854
855 fn compile_thunk_deferred(&mut self, expr: &ast::Expr) -> Result<Vec<UpvalueDesc>, CompileError> {
857 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
858 tc.scope_depth = 1;
859 tc.enclosing = Some(self as *mut Compiler);
860 tc.with_depth = 0;
861 tc.base_dir = self.base_dir.clone();
862 let with_count = self.emit_with_scope_preamble(&mut tc);
863 tc.compile_expr(expr)?;
864 for _ in 0..with_count { tc.emit(OpCode::PopWith); }
865 tc.emit(OpCode::Return);
866 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
867 let closure = VMValue::Closure(VMClosure {
868 chunk: Rc::new(tc.chunk), upvalues: Vec::new(), arity: 0, name: None, formals: Vec::new(),
869 });
870 let idx = self.chunk.add_constant(closure)?;
871 self.emit(OpCode::MakeThunk);
872 self.stack_depth += 1; self.emit_u16(idx);
874 self.emit_u16(0); Ok(uv_descs)
876 }
877
878 fn compile_arg_maybe_thunk(&mut self, arg: &ast::Expr) -> Result<(), CompileError> {
880 if Self::is_trivial_arg(arg) {
881 self.compile_expr(arg)
882 } else {
883 self.compile_thunk_immediate(arg)
884 }
885 }
886
887 fn is_trivial_arg(expr: &ast::Expr) -> bool {
888 match expr {
889 ast::Expr::Literal(_) | ast::Expr::Ident(_)
890 | ast::Expr::PathAbs(_) | ast::Expr::PathRel(_)
891 | ast::Expr::PathHome(_) | ast::Expr::Lambda(_) => true,
892 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_arg(&inner)),
894 ast::Expr::Str(s) => s.normalized_parts().iter().all(|p| matches!(p, InterpolPart::Literal(_))),
896 _ => false,
897 }
898 }
899
900 fn compile_inherit_from_thunk_deferred(
903 &mut self,
904 source_expr: &ast::Expr,
905 attr_name: &str,
906 ) -> Result<Vec<UpvalueDesc>, CompileError> {
907 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
908 tc.scope_depth = 1;
909 tc.enclosing = Some(self as *mut Compiler);
910 tc.with_depth = 0;
911 tc.base_dir = self.base_dir.clone();
912 let with_count = self.emit_with_scope_preamble(&mut tc);
913 tc.compile_expr(source_expr)?;
914 let key_idx = tc.add_attr_key(attr_name.to_string())?;
915 tc.emit(OpCode::GetAttr);
916 tc.emit_u16(key_idx);
917 for _ in 0..with_count { tc.emit(OpCode::PopWith); }
918 tc.emit(OpCode::Return);
919 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
920 let closure = VMValue::Closure(VMClosure {
921 chunk: Rc::new(tc.chunk),
922 upvalues: Vec::new(),
923 arity: 0, formals: Vec::new(),
924 name: None,
925 });
926 let idx = self.chunk.add_constant(closure)?;
927 self.emit(OpCode::MakeThunk);
928 self.stack_depth += 1; self.emit_u16(idx);
930 self.emit_u16(0); Ok(uv_descs)
932 }
933
934 fn compile_nested_attrset_thunk_deferred(
941 &mut self,
942 sub_bindings: &[(Vec<String>, ast::Expr)],
943 ) -> Result<Vec<UpvalueDesc>, CompileError> {
944 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
945 tc.scope_depth = 1;
946 tc.enclosing = Some(self as *mut Compiler);
947 tc.with_depth = 0;
948 tc.base_dir = self.base_dir.clone();
949 let with_count = self.emit_with_scope_preamble(&mut tc);
950 tc.compile_nested_attrset_lazy(sub_bindings)?;
951 for _ in 0..with_count { tc.emit(OpCode::PopWith); }
952 tc.emit(OpCode::Return);
953 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
954 let closure = VMValue::Closure(VMClosure {
955 chunk: Rc::new(tc.chunk), upvalues: Vec::new(), arity: 0, name: None, formals: Vec::new(),
956 });
957 let idx = self.chunk.add_constant(closure)?;
958 self.emit(OpCode::MakeThunk);
959 self.stack_depth += 1; self.emit_u16(idx);
961 self.emit_u16(0); Ok(uv_descs)
963 }
964
965 fn emit_with_scope_preamble(&mut self, tc: &mut Compiler) -> usize {
970 let slots: Vec<u16> = self.with_scope_locals.clone();
971 for &slot in &slots {
972 let local_idx = self.locals.iter().rposition(|l| l.slot == slot);
974 if let Some(idx) = local_idx {
975 self.locals[idx].is_captured = true;
976 if let Ok(uv_idx) = tc.add_upvalue(true, slot) {
977 tc.emit(OpCode::GetUpvalue);
978 tc.emit_u16(uv_idx as u16);
979 tc.emit(OpCode::PushWith);
980 tc.with_depth += 1;
981 }
982 }
983 }
984 slots.len()
985 }
986
987 fn compile_thunk_immediate(&mut self, expr: &ast::Expr) -> Result<(), CompileError> {
994 if let Some(ref source) = self.source_text {
997 if self.locals.is_empty() && self.with_depth == 0 && self.upvalues.is_empty() {
998 let range = AstNode::syntax(expr).text_range();
999 let offset: usize = range.start().into();
1000 let length: usize = range.len().into();
1001 let base_dir_str = self.base_dir
1002 .as_ref()
1003 .map(|p| p.to_string_lossy().to_string())
1004 .unwrap_or_default();
1005
1006 let src_idx = self.chunk.add_constant(VMValue::String((**source).clone()))?;
1008 let dir_idx = self.chunk.add_constant(VMValue::String(base_dir_str))?;
1009
1010 self.emit(OpCode::MakeLazyThunk);
1011 self.stack_depth += 1;
1012 self.emit_u16(src_idx);
1013 self.chunk.write_u32(offset as u32, self.current_line);
1014 self.chunk.write_u32(length as u32, self.current_line);
1015 self.emit_u16(dir_idx);
1016 self.emit_u16(0); return Ok(());
1018 }
1019 }
1020
1021 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
1023 tc.scope_depth = 1;
1024 tc.enclosing = Some(self as *mut Compiler);
1025 tc.with_depth = 0; tc.base_dir = self.base_dir.clone();
1027
1028 let with_count = self.emit_with_scope_preamble(&mut tc);
1031
1032 tc.compile_expr(expr)?;
1033
1034 for _ in 0..with_count {
1036 tc.emit(OpCode::PopWith);
1037 }
1038
1039 tc.emit(OpCode::Return);
1040 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
1041 let closure = VMValue::Closure(VMClosure {
1042 chunk: Rc::new(tc.chunk), upvalues: Vec::new(), arity: 0, name: None, formals: Vec::new(),
1043 });
1044 let idx = self.chunk.add_constant(closure)?;
1045 self.emit(OpCode::MakeThunk);
1046 self.stack_depth += 1; self.emit_u16(idx);
1048 self.emit_u16(uv_descs.len() as u16);
1049 for uv in &uv_descs {
1050 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1051 self.emit_u16(uv.index);
1052 }
1053 Ok(())
1054 }
1055
1056 fn compile_inherit_from_thunk(
1059 &mut self,
1060 source_expr: &ast::Expr,
1061 attr_name: &str,
1062 ) -> Result<(), CompileError> {
1063 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
1064 tc.scope_depth = 1;
1065 tc.enclosing = Some(self as *mut Compiler);
1066 tc.with_depth = 0;
1067 tc.base_dir = self.base_dir.clone();
1068 let with_count = self.emit_with_scope_preamble(&mut tc);
1069 tc.compile_expr(source_expr)?;
1070 let key_idx = tc.add_attr_key(attr_name.to_string())?;
1071 tc.emit(OpCode::GetAttr);
1072 tc.emit_u16(key_idx);
1073 for _ in 0..with_count { tc.emit(OpCode::PopWith); }
1074 tc.emit(OpCode::Return);
1075 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
1076 let closure = VMValue::Closure(VMClosure {
1077 chunk: Rc::new(tc.chunk),
1078 upvalues: Vec::new(),
1079 arity: 0, formals: Vec::new(),
1080 name: None,
1081 });
1082 let idx = self.chunk.add_constant(closure)?;
1083 self.emit(OpCode::MakeThunk);
1084 self.stack_depth += 1; self.emit_u16(idx);
1086 self.emit_u16(uv_descs.len() as u16);
1087 for uv in &uv_descs {
1088 self.chunk
1089 .write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1090 self.emit_u16(uv.index);
1091 }
1092 Ok(())
1093 }
1094
1095 fn compile_attrset(&mut self, set: &ast::AttrSet) -> Result<(), CompileError> {
1098 if set.rec_token().is_some() {
1099 return self.compile_rec_attrset(set);
1100 }
1101
1102 let mut flat_entries: Vec<(String, ast::Expr)> = Vec::new();
1105 let mut dotted_entries: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1106 std::collections::BTreeMap::new();
1107 let mut inherit_entries: Vec<(String, Option<ast::Expr>)> = Vec::new();
1108 let mut dynamic_entries: Vec<(ast::Expr, ast::Expr)> = Vec::new();
1109 let mut dynamic_dotted_entries: Vec<(ast::Attr, Vec<String>, ast::Expr)> = Vec::new();
1110
1111 for entry in set.entries() {
1112 match entry {
1113 ast::Entry::AttrpathValue(ref apv) => {
1114 let attrpath = apv.attrpath().ok_or_else(|| {
1115 CompileError::MissingNode("attrset attrpath".to_string())
1116 })?;
1117 let keys: Vec<_> = attrpath.attrs().collect();
1118 let value_expr = apv.value().ok_or_else(|| {
1119 CompileError::MissingNode("attrset value".to_string())
1120 })?;
1121
1122 if keys.len() == 1 {
1123 match &keys[0] {
1125 ast::Attr::Dynamic(dyn_attr) => {
1126 let key_expr = dyn_attr.expr().ok_or_else(|| {
1127 CompileError::MissingNode("dynamic attr key".to_string())
1128 })?;
1129 dynamic_entries.push((key_expr, value_expr));
1130 }
1131 ast::Attr::Str(s) => {
1132 if let Ok(key) = static_attr_name(&keys[0]) {
1139 flat_entries.push((key, value_expr));
1140 } else {
1141 let key_expr = ast::Expr::Str(s.clone());
1143 dynamic_entries.push((key_expr, value_expr));
1144 }
1145 }
1146 _ => {
1147 let key = static_attr_name(&keys[0])?;
1148 flat_entries.push((key, value_expr));
1149 }
1150 }
1151 } else {
1152 match static_attr_name(&keys[0]) {
1154 Ok(top_key) => {
1155 let rest_keys: Vec<String> = keys[1..]
1156 .iter()
1157 .map(static_attr_name)
1158 .collect::<Result<_, _>>()?;
1159 dotted_entries
1160 .entry(top_key)
1161 .or_default()
1162 .push((rest_keys, value_expr));
1163 }
1164 Err(_) => {
1165 let rest_keys: Vec<String> = keys[1..]
1169 .iter()
1170 .map(static_attr_name)
1171 .collect::<Result<_, _>>()?;
1172 dynamic_dotted_entries.push((
1175 keys[0].clone(),
1176 rest_keys,
1177 value_expr,
1178 ));
1179 }
1180 }
1181 }
1182 }
1183 ast::Entry::Inherit(ref inherit) => {
1184 let source_expr = inherit.from().and_then(|f| f.expr());
1185 for attr in inherit.attrs() {
1186 let name = static_attr_name(&attr)?;
1187 inherit_entries.push((name, source_expr.clone()));
1188 }
1189 }
1190 }
1191 }
1192
1193 let mut count: u16 = 0;
1194
1195 for (key, value_expr) in &flat_entries {
1199 if Self::is_trivial_value(value_expr) {
1200 self.compile_expr(value_expr)?;
1201 } else {
1202 self.compile_thunk_immediate(value_expr)?;
1203 }
1204 self.emit_constant(VMValue::String(key.clone()))?;
1205 count += 1;
1206 }
1207
1208 for (top_key, sub_bindings) in &dotted_entries {
1210 self.compile_nested_attrset(sub_bindings)?;
1211 self.emit_constant(VMValue::String(top_key.clone()))?;
1212 count += 1;
1213 }
1214
1215 for (name, source_expr) in &inherit_entries {
1218 if let Some(src) = source_expr {
1219 self.compile_inherit_from_thunk(src, name)?;
1223 } else {
1224 self.emit_variable_load(name)?;
1226 }
1227 self.emit_constant(VMValue::String(name.clone()))?;
1228 count += 1;
1229 }
1230
1231 for (key_expr, value_expr) in &dynamic_entries {
1234 if Self::is_trivial_value(value_expr) {
1235 self.compile_expr(value_expr)?;
1236 } else {
1237 self.compile_thunk_immediate(value_expr)?;
1238 }
1239 self.compile_expr(key_expr)?;
1240 count += 1;
1241 }
1242
1243 for (key_attr, rest_keys, value_expr) in &dynamic_dotted_entries {
1247 self.compile_nested_attrset(&[(rest_keys.clone(), value_expr.clone())])?;
1249 self.compile_dynamic_attr_key(key_attr)?;
1251 count += 1;
1252 }
1253
1254 self.emit(OpCode::MakeAttrs);
1255 self.emit_u16(count);
1256 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1258
1259 Ok(())
1266 }
1267
1268 fn compile_rec_attrset(&mut self, set: &ast::AttrSet) -> Result<(), CompileError> {
1270 self.begin_scope();
1271
1272 let mut bindings: Vec<(String, RecAttrBinding)> = Vec::new();
1274 let mut dotted_entries: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1275 std::collections::BTreeMap::new();
1276
1277 for entry in set.entries() {
1278 match entry {
1279 ast::Entry::AttrpathValue(ref apv) => {
1280 let attrpath = apv.attrpath().ok_or_else(|| {
1281 CompileError::MissingNode("rec attrset attrpath".to_string())
1282 })?;
1283 let keys: Vec<_> = attrpath.attrs().collect();
1284 let value_expr = apv.value().ok_or_else(|| {
1285 CompileError::MissingNode("rec attrset value".to_string())
1286 })?;
1287 if keys.len() == 1 {
1288 let key = static_attr_name(&keys[0])?;
1289 bindings.push((key, RecAttrBinding::Value(value_expr)));
1290 } else {
1291 let top_key = static_attr_name(&keys[0])?;
1292 let rest_keys: Vec<String> = keys[1..]
1293 .iter()
1294 .map(static_attr_name)
1295 .collect::<Result<_, _>>()?;
1296 dotted_entries
1297 .entry(top_key)
1298 .or_default()
1299 .push((rest_keys, value_expr));
1300 }
1301 }
1302 ast::Entry::Inherit(ref inherit) => {
1303 if let Some(from) = inherit.from() {
1304 let source_expr = from.expr().ok_or_else(|| {
1305 CompileError::MissingNode("inherit from expr".to_string())
1306 })?;
1307 for attr in inherit.attrs() {
1308 let name = static_attr_name(&attr)?;
1309 bindings.push((name.clone(), RecAttrBinding::InheritFrom(source_expr.clone(), name)));
1310 }
1311 } else {
1312 for attr in inherit.attrs() {
1313 let name = static_attr_name(&attr)?;
1314 bindings.push((name, RecAttrBinding::Inherit));
1315 }
1316 }
1317 }
1318 }
1319 }
1320
1321 for (top_key, sub) in &dotted_entries {
1323 bindings.push((top_key.clone(), RecAttrBinding::Dotted(sub.clone())));
1324 }
1325
1326 {
1328 let pairs: Vec<(String, &ast::Expr)> = bindings
1329 .iter()
1330 .filter_map(|(name, binding)| match binding {
1331 RecAttrBinding::Value(expr) => Some((name.clone(), expr as &ast::Expr)),
1332 _ => None,
1333 })
1334 .collect();
1335 for warning in detect_trivial_cycles(&pairs) {
1336 eprintln!("{warning}");
1337 }
1338 }
1339
1340 let binding_count = u16::try_from(bindings.len())
1341 .map_err(|_| CompileError::TooManyLocals)?;
1342
1343 for (name, _) in &bindings {
1345 self.emit(OpCode::Null); self.add_local(name.clone())?;
1347 }
1348
1349 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
1352
1353 for (name, binding) in &bindings {
1354 let local_idx = self.resolve_local(name).unwrap();
1355 let slot = self.locals[local_idx as usize].slot;
1356 match binding {
1357 RecAttrBinding::Value(expr) => {
1358 if Self::is_trivial_value_for_rec(expr) {
1366 self.compile_expr(expr)?;
1367 } else {
1368 let uv_descs = self.compile_thunk_deferred(expr)?;
1369 if !uv_descs.is_empty() {
1370 thunk_slots.push((slot, uv_descs));
1371 }
1372 }
1373 }
1374 RecAttrBinding::Inherit => {
1375 let saved_depth = self.locals[local_idx as usize].depth;
1377 self.locals[local_idx as usize].depth = u32::MAX;
1378 self.emit_variable_load_restore(name, local_idx, saved_depth)?;
1379 self.locals[local_idx as usize].depth = saved_depth;
1380 }
1381 RecAttrBinding::InheritFrom(source_expr, attr_name) => {
1382 let uv_descs = self.compile_inherit_from_thunk_deferred(source_expr, attr_name)?;
1384 if !uv_descs.is_empty() {
1385 thunk_slots.push((slot, uv_descs));
1386 }
1387 }
1388 RecAttrBinding::Dotted(sub_bindings) => {
1389 let uv_descs = self.compile_nested_attrset_thunk_deferred(sub_bindings)?;
1396 if !uv_descs.is_empty() {
1397 thunk_slots.push((slot, uv_descs));
1398 }
1399 }
1400 }
1401 self.emit(OpCode::SetLocal);
1402 self.emit_u16(slot);
1403 self.emit(OpCode::Pop);
1404 }
1405
1406 for (slot, uv_descs) in &thunk_slots {
1408 self.emit(OpCode::PatchThunkUpvalues);
1409 self.emit_u16(*slot);
1410 self.emit_u16(uv_descs.len() as u16);
1411 for uv in uv_descs {
1412 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1413 self.emit_u16(uv.index);
1414 }
1415 }
1416
1417 for (name, _) in &bindings {
1419 let slot = self.find_local_slot(name);
1420 self.emit(OpCode::GetLocal);
1421 self.emit_u16(slot);
1422 self.emit_constant(VMValue::String(name.clone()))?;
1423 }
1424 self.emit(OpCode::MakeAttrs);
1425 self.emit_u16(binding_count);
1426 self.stack_depth = self.stack_depth.saturating_sub(2 * binding_count) + 1;
1428
1429 self.end_scope(binding_count);
1431
1432 Ok(())
1433 }
1434
1435 fn compile_legacy_let(&mut self, ll: &ast::LegacyLet) -> Result<(), CompileError> {
1441 self.begin_scope();
1442
1443 let mut bindings: Vec<(String, RecAttrBinding)> = Vec::new();
1446 let mut dotted_entries: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1447 std::collections::BTreeMap::new();
1448
1449 for entry in ll.entries() {
1450 match entry {
1451 ast::Entry::AttrpathValue(ref apv) => {
1452 let attrpath = apv.attrpath().ok_or_else(|| {
1453 CompileError::MissingNode("legacy let attrpath".to_string())
1454 })?;
1455 let keys: Vec<_> = attrpath.attrs().collect();
1456 let value_expr = apv.value().ok_or_else(|| {
1457 CompileError::MissingNode("legacy let value".to_string())
1458 })?;
1459 if keys.len() == 1 {
1460 let key = static_attr_name(&keys[0])?;
1461 bindings.push((key, RecAttrBinding::Value(value_expr)));
1462 } else {
1463 let top_key = static_attr_name(&keys[0])?;
1464 let rest_keys: Vec<String> = keys[1..]
1465 .iter()
1466 .map(static_attr_name)
1467 .collect::<Result<_, _>>()?;
1468 dotted_entries
1469 .entry(top_key)
1470 .or_default()
1471 .push((rest_keys, value_expr));
1472 }
1473 }
1474 ast::Entry::Inherit(ref inherit) => {
1475 if let Some(from) = inherit.from() {
1476 let source_expr = from.expr().ok_or_else(|| {
1477 CompileError::MissingNode("inherit from expr".to_string())
1478 })?;
1479 for attr in inherit.attrs() {
1480 let name = static_attr_name(&attr)?;
1481 bindings.push((name.clone(), RecAttrBinding::InheritFrom(source_expr.clone(), name)));
1482 }
1483 } else {
1484 for attr in inherit.attrs() {
1485 let name = static_attr_name(&attr)?;
1486 bindings.push((name, RecAttrBinding::Inherit));
1487 }
1488 }
1489 }
1490 }
1491 }
1492
1493 for (top_key, sub) in &dotted_entries {
1495 bindings.push((top_key.clone(), RecAttrBinding::Dotted(sub.clone())));
1496 }
1497
1498 let binding_count = u16::try_from(bindings.len())
1499 .map_err(|_| CompileError::TooManyLocals)?;
1500
1501 for (name, _) in &bindings {
1503 self.emit(OpCode::Null);
1504 self.add_local(name.clone())?;
1505 }
1506
1507 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
1509
1510 for (name, binding) in &bindings {
1511 let local_idx = self.resolve_local(name).unwrap();
1512 let slot = self.locals[local_idx as usize].slot;
1513 match binding {
1514 RecAttrBinding::Value(expr) => {
1515 if Self::is_trivial_value_for_rec(expr) {
1518 self.compile_expr(expr)?;
1519 } else {
1520 let uv_descs = self.compile_thunk_deferred(expr)?;
1521 if !uv_descs.is_empty() {
1522 thunk_slots.push((slot, uv_descs));
1523 }
1524 }
1525 }
1526 RecAttrBinding::Inherit => {
1527 let saved_depth = self.locals[local_idx as usize].depth;
1528 self.locals[local_idx as usize].depth = u32::MAX;
1529 self.emit_variable_load_restore(name, local_idx, saved_depth)?;
1530 self.locals[local_idx as usize].depth = saved_depth;
1531 }
1532 RecAttrBinding::InheritFrom(source_expr, attr_name) => {
1533 let uv_descs = self.compile_inherit_from_thunk_deferred(source_expr, attr_name)?;
1534 if !uv_descs.is_empty() {
1535 thunk_slots.push((slot, uv_descs));
1536 }
1537 }
1538 RecAttrBinding::Dotted(sub_bindings) => {
1539 let uv_descs = self.compile_nested_attrset_thunk_deferred(sub_bindings)?;
1541 if !uv_descs.is_empty() {
1542 thunk_slots.push((slot, uv_descs));
1543 }
1544 }
1545 }
1546 self.emit(OpCode::SetLocal);
1547 self.emit_u16(slot);
1548 self.emit(OpCode::Pop);
1549 }
1550
1551 for (slot, uv_descs) in &thunk_slots {
1553 self.emit(OpCode::PatchThunkUpvalues);
1554 self.emit_u16(*slot);
1555 self.emit_u16(uv_descs.len() as u16);
1556 for uv in uv_descs {
1557 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1558 self.emit_u16(uv.index);
1559 }
1560 }
1561
1562 let body_slot = self.find_local_slot_opt("body").ok_or_else(|| {
1566 CompileError::MissingNode("legacy let missing 'body' binding".to_string())
1567 })?;
1568 self.emit(OpCode::GetLocal);
1569 self.emit_u16(body_slot);
1570
1571 self.end_scope(binding_count);
1573
1574 Ok(())
1575 }
1576
1577 fn compile_nested_attrset(
1584 &mut self,
1585 sub_bindings: &[(Vec<String>, ast::Expr)],
1586 ) -> Result<(), CompileError> {
1587 self.compile_nested_attrset_inner(sub_bindings, false)
1588 }
1589
1590 fn compile_nested_attrset_lazy(
1591 &mut self,
1592 sub_bindings: &[(Vec<String>, ast::Expr)],
1593 ) -> Result<(), CompileError> {
1594 self.compile_nested_attrset_inner(sub_bindings, true)
1595 }
1596
1597 fn compile_nested_attrset_inner(
1598 &mut self,
1599 sub_bindings: &[(Vec<String>, ast::Expr)],
1600 lazy_leaves: bool,
1601 ) -> Result<(), CompileError> {
1602 let mut groups: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1604 std::collections::BTreeMap::new();
1605
1606 for (path, expr) in sub_bindings {
1607 if path.len() == 1 {
1608 groups
1610 .entry(path[0].clone())
1611 .or_default()
1612 .push((vec![], expr.clone()));
1613 } else {
1614 groups
1616 .entry(path[0].clone())
1617 .or_default()
1618 .push((path[1..].to_vec(), expr.clone()));
1619 }
1620 }
1621
1622 let mut count: u16 = 0;
1623 for (key, nested) in &groups {
1624 if nested.len() == 1 && nested[0].0.is_empty() {
1625 if lazy_leaves && !Self::is_trivial_value(&nested[0].1) {
1627 self.compile_thunk_immediate(&nested[0].1)?;
1628 } else {
1629 self.compile_expr(&nested[0].1)?;
1630 }
1631 } else {
1632 self.compile_nested_attrset_inner(nested, lazy_leaves)?;
1634 }
1635 self.emit_constant(VMValue::String(key.clone()))?;
1636 count += 1;
1637 }
1638
1639 self.emit(OpCode::MakeAttrs);
1640 self.emit_u16(count);
1641 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1642 Ok(())
1643 }
1644
1645 fn emit_variable_load(&mut self, name: &str) -> Result<(), CompileError> {
1647 if let Some(idx) = self.resolve_local(name) {
1648 self.emit(OpCode::GetLocal);
1649 self.emit_u16(self.local_stack_slot(idx));
1650 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
1651 self.emit(OpCode::GetUpvalue);
1652 self.emit_u16(uv_idx as u16);
1653 } else if self.has_with_scope() {
1654 let name_idx = self.chunk.add_constant(VMValue::String(name.to_string()))?;
1655 self.emit(OpCode::LookupWith);
1656 self.emit_u16(name_idx);
1657 } else {
1658 return Err(CompileError::Unsupported(format!(
1659 "inherit: cannot resolve '{name}'"
1660 )));
1661 }
1662 Ok(())
1663 }
1664
1665 fn emit_variable_load_restore(
1668 &mut self,
1669 name: &str,
1670 local_idx: u16,
1671 saved_depth: u32,
1672 ) -> Result<(), CompileError> {
1673 if let Some(outer_idx) = self.resolve_local(name) {
1674 self.emit(OpCode::GetLocal);
1675 self.emit_u16(self.local_stack_slot(outer_idx));
1676 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
1677 self.emit(OpCode::GetUpvalue);
1678 self.emit_u16(uv_idx as u16);
1679 } else if self.has_with_scope() {
1680 let name_idx = self.chunk.add_constant(VMValue::String(name.to_string()))?;
1681 self.emit(OpCode::LookupWith);
1682 self.emit_u16(name_idx);
1683 } else {
1684 self.locals[local_idx as usize].depth = saved_depth;
1685 return Err(CompileError::Unsupported(format!(
1686 "inherit: cannot resolve '{name}' in enclosing scope"
1687 )));
1688 }
1689 Ok(())
1690 }
1691
1692 fn try_resolve_as_local(&self, expr: &ast::Expr) -> Option<u16> {
1696 if let ast::Expr::Ident(id) = expr {
1697 let name = ident_text(id);
1698 let idx = self.resolve_local(&name)?;
1699 Some(self.local_stack_slot(idx))
1700 } else {
1701 None
1702 }
1703 }
1704
1705 fn compile_select(&mut self, sel: &ast::Select) -> Result<(), CompileError> {
1706 let base = sel
1707 .expr()
1708 .ok_or_else(|| CompileError::MissingNode("select base".to_string()))?;
1709 let attrpath = sel
1710 .attrpath()
1711 .ok_or_else(|| CompileError::MissingNode("select attrpath".to_string()))?;
1712
1713 let segments: Vec<_> = attrpath.attrs().collect();
1714
1715 if let Some(default_expr) = sel.default_expr() {
1716 self.compile_expr(&base)?;
1740 let depth_before = self.stack_depth; let mut miss_jumps: Vec<usize> = Vec::new();
1742 for (_i, attr) in segments.iter().enumerate() {
1743 if let Ok(key) = static_attr_name(attr) {
1744 let key_idx = self.add_attr_key(key)?;
1745 self.emit(OpCode::Dup); self.emit(OpCode::HasAttr); self.emit_u16(key_idx);
1748 miss_jumps.push(self.emit_jump(OpCode::JumpIfFalse)); self.emit(OpCode::GetAttr); self.emit_u16(key_idx);
1751 } else {
1752 self.emit(OpCode::Dup); self.compile_dynamic_attr_key(attr)?; self.emit(OpCode::DynHasAttr); miss_jumps.push(self.emit_jump(OpCode::JumpIfFalse)); self.compile_dynamic_attr_key(attr)?; self.emit(OpCode::DynGetAttr); }
1759 }
1760 let end_jump = self.emit_jump(OpCode::Jump);
1763 for mj in miss_jumps {
1765 self.patch_jump(mj)?;
1766 }
1767 self.stack_depth = depth_before;
1769 self.emit(OpCode::Pop); self.compile_expr(&default_expr)?; self.patch_jump(end_jump)?;
1772 } else {
1774 let local_slot = self.try_resolve_as_local(&base);
1777
1778 for (i, attr) in segments.iter().enumerate() {
1779 if let Ok(key) = static_attr_name(attr) {
1780 let key_idx = self.add_attr_key(key)?;
1781
1782 if i == 0 {
1783 if let Some(slot) = local_slot {
1784 self.emit(OpCode::GetLocalAttr);
1786 self.emit_u16(slot);
1787 self.emit_u16(key_idx);
1788 } else {
1789 self.compile_expr(&base)?;
1790 self.emit(OpCode::GetAttr);
1791 self.emit_u16(key_idx);
1792 }
1793 } else {
1794 self.emit(OpCode::GetAttr);
1795 self.emit_u16(key_idx);
1796 }
1797 } else {
1798 if i == 0 {
1800 self.compile_expr(&base)?;
1801 }
1802 self.compile_dynamic_attr_key(attr)?;
1803 self.emit(OpCode::DynGetAttr);
1804 }
1805 }
1806 }
1807
1808 Ok(())
1809 }
1810
1811 fn compile_dynamic_attr_key(&mut self, attr: &ast::Attr) -> Result<(), CompileError> {
1813 match attr {
1814 ast::Attr::Dynamic(d) => {
1815 let expr = d.expr().ok_or_else(|| {
1816 CompileError::MissingNode("dynamic attr key expr".to_string())
1817 })?;
1818 self.compile_expr(&expr)
1819 }
1820 ast::Attr::Str(s) => {
1821 let key_expr = ast::Expr::Str(s.clone());
1822 self.compile_expr(&key_expr)
1823 }
1824 ast::Attr::Ident(ident) => {
1825 self.emit_constant(VMValue::String(ident_text(ident)))
1826 }
1827 }
1828 }
1829
1830 fn compile_has_attr(&mut self, ha: &ast::HasAttr) -> Result<(), CompileError> {
1833 let base = ha
1834 .expr()
1835 .ok_or_else(|| CompileError::MissingNode("hasattr base".to_string()))?;
1836 let attrpath = ha
1837 .attrpath()
1838 .ok_or_else(|| CompileError::MissingNode("hasattr attrpath".to_string()))?;
1839
1840 let segments: Vec<_> = attrpath.attrs().collect();
1841
1842 if segments.len() == 1 {
1843 self.compile_expr(&base)?;
1845 if let Ok(key) = static_attr_name(&segments[0]) {
1846 let key_idx = self.add_attr_key(key)?;
1847 self.emit(OpCode::HasAttr);
1848 self.emit_u16(key_idx);
1849 } else {
1850 self.compile_dynamic_attr_key(&segments[0])?;
1851 self.emit(OpCode::DynHasAttr);
1852 }
1853 return Ok(());
1854 }
1855
1856 let mut false_jumps: Vec<usize> = Vec::new();
1865 let depth_before = self.stack_depth;
1868
1869 for (i, seg) in segments.iter().enumerate() {
1870 self.compile_expr(&base)?;
1872 for prev_seg in &segments[..i] {
1873 if let Ok(prev_key) = static_attr_name(prev_seg) {
1874 let prev_idx = self.add_attr_key(prev_key)?;
1875 self.emit(OpCode::GetAttr);
1876 self.emit_u16(prev_idx);
1877 } else {
1878 self.compile_dynamic_attr_key(prev_seg)?;
1879 self.emit(OpCode::DynGetAttr);
1880 }
1881 }
1882 if let Ok(key) = static_attr_name(seg) {
1883 let key_idx = self.add_attr_key(key)?;
1884 self.emit(OpCode::HasAttr);
1885 self.emit_u16(key_idx);
1886 } else {
1887 self.compile_dynamic_attr_key(seg)?;
1888 self.emit(OpCode::DynHasAttr);
1889 }
1890
1891 if i < segments.len() - 1 {
1893 false_jumps.push(self.emit_jump(OpCode::JumpIfFalse));
1894 self.stack_depth = depth_before;
1899 }
1900 }
1901
1902 let done_jump = self.emit_jump(OpCode::Jump);
1904
1905 self.stack_depth = depth_before;
1908 for fj in false_jumps {
1909 self.patch_jump(fj)?;
1910 }
1911 self.emit(OpCode::False);
1912 self.patch_jump(done_jump)?;
1915 Ok(())
1916 }
1917
1918 fn compile_if(&mut self, ie: &ast::IfElse) -> Result<(), CompileError> {
1921 let cond = ie
1922 .condition()
1923 .ok_or_else(|| CompileError::MissingNode("if condition".to_string()))?;
1924 let then_body = ie
1925 .body()
1926 .ok_or_else(|| CompileError::MissingNode("if then".to_string()))?;
1927 let else_body = ie
1928 .else_body()
1929 .ok_or_else(|| CompileError::MissingNode("if else".to_string()))?;
1930
1931 let tail = self.tail_position;
1933
1934 self.tail_position = false;
1936 self.compile_expr(&cond)?;
1937 let else_jump = self.emit_jump(OpCode::JumpIfFalse);
1939 let depth_at_branch = self.stack_depth;
1942 self.tail_position = tail;
1944 self.compile_expr(&then_body)?;
1945 let end_jump = self.emit_jump(OpCode::Jump);
1947 self.stack_depth = depth_at_branch;
1950 self.patch_jump(else_jump)?;
1951 self.tail_position = tail;
1953 self.compile_expr(&else_body)?;
1954 self.patch_jump(end_jump)?;
1958 Ok(())
1959 }
1960
1961 fn compile_lambda(&mut self, lam: &ast::Lambda) -> Result<(), CompileError> {
1964 let param = lam
1965 .param()
1966 .ok_or_else(|| CompileError::MissingNode("lambda param".to_string()))?;
1967 let body = lam
1968 .body()
1969 .ok_or_else(|| CompileError::MissingNode("lambda body".to_string()))?;
1970
1971 let mut func_compiler = Compiler::with_interner(Rc::clone(&self.interner));
1973 func_compiler.scope_depth = 1; func_compiler.enclosing = Some(self as *mut Compiler);
1976 func_compiler.base_dir = self.base_dir.clone();
1978 func_compiler.stack_depth = 1;
1980
1981 let mut formals_metadata: Vec<(String, bool)> = Vec::new();
1982 let (arity, name) = match ¶m {
1983 ast::Param::IdentParam(ip) => {
1984 let ident = ip
1985 .ident()
1986 .ok_or_else(|| CompileError::MissingNode("lambda ident".to_string()))?;
1987 let name = ident_text(&ident);
1988 func_compiler.add_local(name.clone())?;
1990 (1, Some(name))
1991 }
1992 ast::Param::Pattern(pat) => {
1993 let bind_name = pat
1997 .pat_bind()
1998 .and_then(|pb| pb.ident())
1999 .map(|id| ident_text(&id));
2000
2001 if let Some(ref bname) = bind_name {
2002 func_compiler.add_local(bname.clone())?;
2003 } else {
2004 func_compiler.add_local("__arg".to_string())?;
2006 }
2007
2008 let mut field_names: Vec<(String, Option<ast::Expr>)> = Vec::new();
2010 for entry in pat.pat_entries() {
2011 let ident = entry
2012 .ident()
2013 .ok_or_else(|| CompileError::MissingNode("pattern entry ident".to_string()))?;
2014 let fname = ident_text(&ident);
2015 let default = entry.default();
2016 formals_metadata.push((fname.clone(), default.is_some()));
2017 field_names.push((fname, default));
2018 }
2019
2020 for (fname, _) in &field_names {
2022 func_compiler.emit(OpCode::Null); func_compiler.add_local(fname.clone())?;
2024 }
2025
2026 for (i, (fname, default)) in field_names.iter().enumerate() {
2028 let key_idx = func_compiler.add_attr_key(fname.clone())?;
2029 if let Some(default_expr) = default {
2030 func_compiler.emit(OpCode::GetLocal);
2048 func_compiler.emit_u16(0); func_compiler.emit(OpCode::HasAttr);
2050 func_compiler.emit_u16(key_idx);
2051 let else_jump = func_compiler.emit_jump(OpCode::JumpIfFalse);
2052 let depth_at_branch = func_compiler.stack_depth;
2054 func_compiler.emit(OpCode::GetLocal);
2056 func_compiler.emit_u16(0);
2057 func_compiler.emit(OpCode::GetAttr);
2058 func_compiler.emit_u16(key_idx);
2059 let end_jump = func_compiler.emit_jump(OpCode::Jump);
2060 func_compiler.stack_depth = depth_at_branch;
2062 func_compiler.patch_jump(else_jump)?;
2063 func_compiler.compile_thunk_immediate(default_expr)?;
2064 func_compiler.patch_jump(end_jump)?;
2066 } else {
2067 func_compiler.emit(OpCode::GetLocal);
2069 func_compiler.emit_u16(0); func_compiler.emit(OpCode::GetAttr);
2071 func_compiler.emit_u16(key_idx);
2072 }
2073 let field_slot = func_compiler.find_local_slot(fname);
2075 func_compiler.emit(OpCode::SetLocal);
2076 func_compiler.emit_u16(field_slot);
2077 func_compiler.emit(OpCode::Pop);
2078 let _ = i; }
2080
2081 (1, bind_name)
2082 }
2083 };
2084
2085 func_compiler.tail_position = true;
2088 func_compiler.compile_expr(&body)?;
2089 func_compiler.emit(OpCode::Return);
2090
2091 let upvalue_count = func_compiler.upvalues.len();
2093 let upvalue_descs: Vec<UpvalueDesc> = func_compiler.upvalues.clone();
2094
2095 let closure = VMValue::Closure(VMClosure {
2097 chunk: Rc::new(func_compiler.chunk),
2098 upvalues: Vec::new(), arity,
2100 name,
2101 formals: formals_metadata,
2102 });
2103
2104 if upvalue_count == 0 {
2105 self.emit_constant(closure)
2107 } else {
2108 let idx = self.chunk.add_constant(closure)?;
2110 self.emit(OpCode::MakeClosure);
2111 self.stack_depth += 1; self.emit_u16(idx);
2113 self.emit_u16(upvalue_count as u16);
2115 for uv in &upvalue_descs {
2117 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
2118 self.emit_u16(uv.index);
2119 }
2120 Ok(())
2121 }
2122 }
2123
2124 fn compile_apply(&mut self, app: &ast::Apply) -> Result<(), CompileError> {
2127 let func = app
2128 .lambda()
2129 .ok_or_else(|| CompileError::MissingNode("apply function".to_string()))?;
2130 let arg = app
2131 .argument()
2132 .ok_or_else(|| CompileError::MissingNode("apply argument".to_string()))?;
2133
2134 let tail = self.tail_position;
2136 self.tail_position = false;
2137
2138 if let ast::Expr::Ident(ref id) = func {
2140 let name = ident_text(id);
2141 if name == "import" {
2142 self.compile_expr(&arg)?;
2143 self.emit(OpCode::Import);
2144 return Ok(());
2145 }
2146 }
2147
2148 let call_op = if tail { OpCode::TailCall } else { OpCode::Call };
2150
2151 if !tail {
2155 if let Some(slot) = self.try_resolve_as_local(&func) {
2156 self.compile_arg_maybe_thunk(&arg)?;
2157 self.emit(OpCode::GetLocalCall);
2158 self.emit_u16(slot);
2159 return Ok(());
2160 }
2161 }
2162
2163 self.compile_expr(&func)?;
2165 self.compile_arg_maybe_thunk(&arg)?;
2166 self.emit(call_op);
2167 Ok(())
2168 }
2169
2170 fn compile_binop(&mut self, binop: &ast::BinOp) -> Result<(), CompileError> {
2178 let lhs = binop
2179 .lhs()
2180 .ok_or_else(|| CompileError::MissingNode("binop lhs".to_string()))?;
2181 let rhs = binop
2182 .rhs()
2183 .ok_or_else(|| CompileError::MissingNode("binop rhs".to_string()))?;
2184 let op = binop
2185 .operator()
2186 .ok_or_else(|| CompileError::MissingNode("binop operator".to_string()))?;
2187
2188 match op {
2189 ast::BinOpKind::And => {
2191 self.compile_expr(&lhs)?;
2192 let false_jump = self.emit_jump(OpCode::JumpIfFalse);
2193 let depth_at_branch = self.stack_depth;
2195 self.compile_expr(&rhs)?;
2196 let end_jump = self.emit_jump(OpCode::Jump);
2197 self.stack_depth = depth_at_branch;
2199 self.patch_jump(false_jump)?;
2200 self.emit(OpCode::False);
2201 self.patch_jump(end_jump)?;
2202 }
2203 ast::BinOpKind::Or => {
2205 self.compile_expr(&lhs)?;
2206 let true_jump = self.emit_jump(OpCode::JumpIfTrue);
2207 let depth_at_branch = self.stack_depth;
2209 self.compile_expr(&rhs)?;
2210 let end_jump = self.emit_jump(OpCode::Jump);
2211 self.stack_depth = depth_at_branch;
2213 self.patch_jump(true_jump)?;
2214 self.emit(OpCode::True);
2215 self.patch_jump(end_jump)?;
2216 }
2217 ast::BinOpKind::Implication => {
2219 self.compile_expr(&lhs)?;
2220 let false_jump = self.emit_jump(OpCode::JumpIfFalse);
2221 let depth_at_branch = self.stack_depth;
2223 self.compile_expr(&rhs)?;
2224 let end_jump = self.emit_jump(OpCode::Jump);
2225 self.stack_depth = depth_at_branch;
2227 self.patch_jump(false_jump)?;
2228 self.emit(OpCode::True);
2229 self.patch_jump(end_jump)?;
2230 }
2231 _ => {
2233 self.compile_expr(&lhs)?;
2234 self.compile_expr(&rhs)?;
2235 match op {
2236 ast::BinOpKind::Add => self.emit(OpCode::Add),
2237 ast::BinOpKind::Sub => self.emit(OpCode::Sub),
2238 ast::BinOpKind::Mul => self.emit(OpCode::Mul),
2239 ast::BinOpKind::Div => self.emit(OpCode::Div),
2240 ast::BinOpKind::Equal => self.emit(OpCode::Equal),
2241 ast::BinOpKind::NotEqual => self.emit(OpCode::NotEqual),
2242 ast::BinOpKind::Less => self.emit(OpCode::Less),
2243 ast::BinOpKind::LessOrEq => self.emit(OpCode::LessEqual),
2244 ast::BinOpKind::More => self.emit(OpCode::Greater),
2245 ast::BinOpKind::MoreOrEq => self.emit(OpCode::GreaterEqual),
2246 ast::BinOpKind::Update => self.emit(OpCode::UpdateAttrs),
2247 ast::BinOpKind::Concat => self.emit(OpCode::Concat),
2248 ast::BinOpKind::And
2249 | ast::BinOpKind::Or
2250 | ast::BinOpKind::Implication => unreachable!(),
2251 ast::BinOpKind::PipeRight | ast::BinOpKind::PipeLeft => {
2252 return Err(CompileError::Unsupported("pipe operators".to_string()));
2253 }
2254 }
2255 }
2256 }
2257 Ok(())
2258 }
2259
2260 fn compile_unary(&mut self, op: &ast::UnaryOp) -> Result<(), CompileError> {
2263 let inner = op
2264 .expr()
2265 .ok_or_else(|| CompileError::MissingNode("unary expr".to_string()))?;
2266 let kind = op
2267 .operator()
2268 .ok_or_else(|| CompileError::MissingNode("unary operator".to_string()))?;
2269 self.compile_expr(&inner)?;
2270 match kind {
2271 ast::UnaryOpKind::Negate => self.emit(OpCode::Negate),
2272 ast::UnaryOpKind::Invert => self.emit(OpCode::Not),
2273 }
2274 Ok(())
2275 }
2276
2277 fn compile_with(&mut self, with: &ast::With) -> Result<(), CompileError> {
2280 let ns = with
2281 .namespace()
2282 .ok_or_else(|| CompileError::MissingNode("with namespace".to_string()))?;
2283 let body = with
2284 .body()
2285 .ok_or_else(|| CompileError::MissingNode("with body".to_string()))?;
2286
2287 self.compile_expr(&ns)?;
2289
2290 self.emit(OpCode::Dup);
2294 self.emit(OpCode::PushWith);
2295
2296 let slot = self.add_local("__with_scope".to_string())?;
2298 self.with_scope_locals.push(slot);
2299 self.with_depth += 1;
2300
2301 self.compile_expr(&body)?;
2303
2304 self.emit(OpCode::PopWith);
2306 self.with_depth -= 1;
2307 self.with_scope_locals.pop();
2308
2309 self.emit(OpCode::SetLocal);
2315 self.emit_u16(slot);
2316 self.emit(OpCode::Pop);
2317 self.stack_depth = slot + 1;
2319 self.locals.pop();
2320
2321 Ok(())
2322 }
2323
2324 fn compile_assert(&mut self, assert: &ast::Assert) -> Result<(), CompileError> {
2327 let cond = assert
2328 .condition()
2329 .ok_or_else(|| CompileError::MissingNode("assert condition".to_string()))?;
2330 let body = assert
2331 .body()
2332 .ok_or_else(|| CompileError::MissingNode("assert body".to_string()))?;
2333 let tail = self.tail_position;
2335 self.tail_position = false;
2336 self.compile_expr(&cond)?;
2337 self.emit(OpCode::Assert);
2338 self.tail_position = tail;
2340 self.compile_expr(&body)?;
2341 Ok(())
2342 }
2343
2344 fn compile_list(&mut self, list: &ast::List) -> Result<(), CompileError> {
2347 let items: Vec<_> = list.items().collect();
2348 let count = u16::try_from(items.len())
2349 .map_err(|_| CompileError::Unsupported("list too large".to_string()))?;
2350 for item in &items {
2351 self.compile_expr(item)?;
2352 }
2353 self.emit(OpCode::MakeList);
2354 self.emit_u16(count);
2355 self.stack_depth = self.stack_depth.saturating_sub(count) + 1;
2357 Ok(())
2358 }
2359
2360 fn emit(&mut self, op: OpCode) {
2363 self.chunk.write_op(op, self.current_line);
2364 match op {
2366 OpCode::Null | OpCode::True | OpCode::False
2368 | OpCode::GetLocal | OpCode::GetUpvalue
2369 | OpCode::PushBuiltins | OpCode::LookupWith => {
2370 self.stack_depth += 1;
2371 }
2372 OpCode::Dup => {
2374 self.stack_depth += 1;
2375 }
2376 OpCode::Pop | OpCode::PushWith
2378 | OpCode::Assert | OpCode::Throw | OpCode::Return => {
2379 self.stack_depth = self.stack_depth.saturating_sub(1);
2380 }
2381 OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div
2383 | OpCode::Equal | OpCode::NotEqual | OpCode::Less
2384 | OpCode::Greater | OpCode::LessEqual | OpCode::GreaterEqual
2385 | OpCode::And | OpCode::Or | OpCode::Implication
2386 | OpCode::Concat | OpCode::UpdateAttrs
2387 | OpCode::Call | OpCode::TailCall | OpCode::DynGetAttr | OpCode::DynHasAttr => {
2388 self.stack_depth = self.stack_depth.saturating_sub(1);
2389 }
2390 OpCode::Negate | OpCode::Not | OpCode::Force
2392 | OpCode::GetAttr | OpCode::HasAttr
2393 | OpCode::Import => {}
2394 OpCode::SetLocal | OpCode::SetUpvalue => {}
2396 OpCode::PopWith => {}
2398 OpCode::Jump => {}
2400 OpCode::JumpIfFalse | OpCode::JumpIfTrue => {
2402 self.stack_depth = self.stack_depth.saturating_sub(1);
2403 }
2404 OpCode::SelectOrDefault => {
2406 self.stack_depth = self.stack_depth.saturating_sub(1);
2407 }
2408 OpCode::DynSelectOrDefault => {
2410 self.stack_depth = self.stack_depth.saturating_sub(2);
2411 }
2412 OpCode::GetLocalAttr => {
2414 self.stack_depth += 1;
2415 }
2416 OpCode::GetLocalCall => {
2418 self.stack_depth = self.stack_depth.saturating_sub(1);
2419 }
2420 OpCode::CallBuiltin => {
2422 self.stack_depth = self.stack_depth.saturating_sub(1);
2423 }
2424 OpCode::Constant | OpCode::MakeAttrs | OpCode::MakeList
2432 | OpCode::MakeClosure | OpCode::MakeThunk | OpCode::MakeLazyThunk
2433 | OpCode::Interpolate | OpCode::PatchThunkUpvalues => {}
2434 }
2435 }
2436
2437
2438 fn emit_u16(&mut self, value: u16) {
2439 self.chunk.write_u16(value, self.current_line);
2440 }
2441
2442 fn emit_constant(&mut self, value: VMValue) -> Result<(), CompileError> {
2443 let idx = self.chunk.add_constant(value)?;
2444 self.emit(OpCode::Constant);
2445 self.stack_depth += 1; self.emit_u16(idx);
2447 Ok(())
2448 }
2449
2450 fn add_attr_key(&mut self, key: String) -> Result<u16, CompileError> {
2455 let sym = self.interner.borrow_mut().intern(&key);
2456 self.chunk.add_key_constant(VMValue::String(key), sym)
2457 }
2458
2459 fn emit_jump(&mut self, op: OpCode) -> usize {
2462 self.emit(op);
2463 let offset = self.chunk.len();
2464 self.emit_u16(0xFFFF); offset
2466 }
2467
2468 fn patch_jump(&mut self, placeholder_offset: usize) -> Result<(), CompileError> {
2470 let target = self.chunk.len();
2471 let target_u16 = u16::try_from(target).map_err(|_| CompileError::JumpOverflow)?;
2472 self.chunk.patch_u16(placeholder_offset, target_u16);
2473 Ok(())
2474 }
2475
2476 fn begin_scope(&mut self) {
2479 self.scope_depth += 1;
2480 }
2481
2482 fn end_scope(&mut self, binding_count: u16) {
2483 if binding_count > 0 {
2554 let first_local_idx = self.locals.len() - binding_count as usize;
2558 let base_slot = self.locals[first_local_idx].slot;
2559 self.emit(OpCode::SetLocal);
2560 self.emit_u16(base_slot);
2561 for _ in 0..binding_count {
2562 self.emit(OpCode::Pop);
2563 }
2564 self.stack_depth = base_slot + 1;
2567 }
2568
2569 while let Some(local) = self.locals.last() {
2571 if local.depth < self.scope_depth {
2572 break;
2573 }
2574 self.locals.pop();
2575 }
2576 self.scope_depth -= 1;
2577 }
2578
2579 fn add_local(&mut self, name: String) -> Result<u16, CompileError> {
2581 if self.locals.len() >= u16::MAX as usize {
2582 return Err(CompileError::TooManyLocals);
2583 }
2584 let slot = self.stack_depth - 1;
2588 self.locals.push(Local {
2589 name,
2590 depth: self.scope_depth,
2591 is_captured: false,
2592 slot,
2593 });
2594 Ok(slot)
2595 }
2596
2597 fn resolve_local(&self, name: &str) -> Option<u16> {
2600 for (i, local) in self.locals.iter().enumerate().rev() {
2601 if local.name == name && local.depth != u32::MAX {
2602 return Some(i as u16);
2603 }
2604 }
2605 None
2606 }
2607
2608 fn local_stack_slot(&self, locals_idx: u16) -> u16 {
2610 self.locals[locals_idx as usize].slot
2611 }
2612
2613 fn find_local_slot(&self, name: &str) -> u16 {
2617 let idx = self.resolve_local(name)
2618 .unwrap_or_else(|| panic!("local '{name}' not found"));
2619 self.locals[idx as usize].slot
2620 }
2621
2622 fn find_local_slot_opt(&self, name: &str) -> Option<u16> {
2624 self.resolve_local(name)
2625 .map(|idx| self.locals[idx as usize].slot)
2626 }
2627
2628 fn add_upvalue(&mut self, is_local: bool, index: u16) -> Result<u8, CompileError> {
2632 for (i, uv) in self.upvalues.iter().enumerate() {
2634 if uv.is_local == is_local && uv.index == index {
2635 return Ok(i as u8);
2636 }
2637 }
2638 if self.upvalues.len() >= 256 {
2639 return Err(CompileError::Unsupported("too many upvalues (max 256)".to_string()));
2640 }
2641 let idx = self.upvalues.len() as u8;
2642 self.upvalues.push(UpvalueDesc { is_local, index });
2643 Ok(idx)
2644 }
2645
2646 fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
2651 let enclosing_ptr = self.enclosing?;
2652 let enclosing = unsafe { &mut *enclosing_ptr };
2656
2657 if let Some(local_idx) = enclosing.resolve_local(name) {
2659 enclosing.locals[local_idx as usize].is_captured = true;
2660 let stack_slot = enclosing.locals[local_idx as usize].slot;
2662 return Some(self.add_upvalue(true, stack_slot).ok()?);
2663 }
2664
2665 if let Some(uv_idx) = enclosing.resolve_upvalue(name) {
2667 return Some(self.add_upvalue(false, uv_idx as u16).ok()?);
2668 }
2669
2670 None
2676 }
2677
2678 fn has_with_scope(&self) -> bool {
2680 if self.with_depth > 0 {
2681 return true;
2682 }
2683 if let Some(enclosing_ptr) = self.enclosing {
2684 let enclosing = unsafe { &*enclosing_ptr };
2685 return enclosing.has_with_scope();
2686 }
2687 false
2688 }
2689
2690 fn resolve_relative_path(&self, rel_path: &str) -> String {
2693 if let Some(ref base) = self.base_dir {
2694 return base.join(rel_path).to_string_lossy().to_string();
2695 }
2696 if let Some(enclosing_ptr) = self.enclosing {
2697 let enclosing = unsafe { &*enclosing_ptr };
2698 return enclosing.resolve_relative_path(rel_path);
2699 }
2700 rel_path.to_string()
2701 }
2702}
2703
2704fn ident_text(ident: &ast::Ident) -> String {
2708 ident
2709 .ident_token()
2710 .map(|t| t.text().to_string())
2711 .unwrap_or_default()
2712}
2713
2714fn static_attr_name(attr: &ast::Attr) -> Result<String, CompileError> {
2717 match attr {
2718 ast::Attr::Ident(ident) => Ok(ident_text(ident)),
2719 ast::Attr::Str(s) => {
2720 let parts: Vec<_> = s.normalized_parts().into_iter().collect();
2722 if parts.len() == 1 {
2723 if let InterpolPart::Literal(text) = &parts[0] {
2724 return Ok(text.to_string());
2725 }
2726 }
2727 Err(CompileError::Unsupported(
2728 "interpolated string attribute keys".to_string(),
2729 ))
2730 }
2731 ast::Attr::Dynamic(_) => Err(CompileError::Unsupported(
2732 "dynamic attribute keys".to_string(),
2733 )),
2734 }
2735}
2736
2737fn is_global_builtin(name: &str) -> bool {
2771 matches!(
2772 name,
2773 "abort"
2774 | "baseNameOf"
2775 | "break"
2776 | "derivation"
2777 | "derivationStrict"
2778 | "dirOf"
2779 | "fetchGit"
2780 | "fetchMercurial"
2781 | "fetchTarball"
2782 | "fetchTree"
2783 | "fromTOML"
2784 | "import"
2785 | "isNull"
2786 | "map"
2787 | "placeholder"
2788 | "removeAttrs"
2789 | "scopedImport"
2790 | "throw"
2791 | "toString"
2792 )
2793}
2794
2795fn line_of(expr: &ast::Expr) -> u32 {
2797 let offset = AstNode::syntax(expr).text_range().start();
2800 u32::from(offset)
2802}
2803
2804fn detect_trivial_cycles(bindings: &[(String, &ast::Expr)]) -> Vec<String> {
2813 let mut warnings = Vec::new();
2814 for (name, expr) in bindings {
2815 if let ast::Expr::Ident(id) = expr {
2816 if id
2817 .ident_token()
2818 .map(|t| t.text() == name.as_str())
2819 .unwrap_or(false)
2820 {
2821 warnings.push(format!("warning: `{name}` directly references itself"));
2822 }
2823 }
2824 }
2825 warnings
2826}
2827
2828fn parse_nix_path(s: &str) -> Vec<(String, String)> {
2834 if s.is_empty() {
2835 return Vec::new();
2836 }
2837 s.split(':')
2838 .filter(|e| !e.is_empty())
2839 .map(|entry| match entry.split_once('=') {
2840 Some((prefix, path)) => (prefix.to_string(), path.to_string()),
2841 None => (String::new(), entry.to_string()),
2842 })
2843 .collect()
2844}
2845
2846fn resolve_search_path(name: &str) -> Option<String> {
2849 let nix_path = std::env::var("NIX_PATH").ok()?;
2850 for (prefix, path) in parse_nix_path(&nix_path) {
2851 if !prefix.is_empty() && name == prefix {
2852 if std::path::Path::new(&path).exists() {
2853 return Some(path);
2854 }
2855 continue;
2856 }
2857 if !prefix.is_empty() {
2858 let needle = format!("{prefix}/");
2859 if let Some(rest) = name.strip_prefix(&needle) {
2860 let full = format!("{path}/{rest}");
2861 if std::path::Path::new(&full).exists() {
2862 return Some(full);
2863 }
2864 continue;
2865 }
2866 }
2867 if prefix.is_empty() {
2868 let full = format!("{path}/{name}");
2869 if std::path::Path::new(&full).exists() {
2870 return Some(full);
2871 }
2872 }
2873 }
2874 None
2875}
2876
2877#[cfg(test)]
2878mod tests {
2879 use super::*;
2880
2881 fn compile(input: &str) -> Chunk {
2882 let (chunk, _interner) =
2883 Compiler::compile(input).unwrap_or_else(|e| panic!("compile failed for '{input}': {e}"));
2884 chunk
2885 }
2886
2887 #[test]
2888 fn compile_integer() {
2889 let chunk = compile("42");
2890 assert!(!chunk.code.is_empty());
2891 assert_eq!(chunk.constants.len(), 1);
2892 assert_eq!(chunk.constants[0], VMValue::Int(42));
2893 }
2894
2895 #[test]
2896 fn compile_float() {
2897 let chunk = compile("3.14");
2898 assert_eq!(chunk.constants[0], VMValue::Float(3.14));
2899 }
2900
2901 #[test]
2902 fn compile_bool_true() {
2903 let chunk = compile("true");
2904 assert_eq!(chunk.code[0], OpCode::Constant as u8);
2906 assert_eq!(chunk.constants[0], VMValue::Bool(true));
2907 }
2908
2909 #[test]
2910 fn compile_bool_false() {
2911 let chunk = compile("false");
2912 assert_eq!(chunk.code[0], OpCode::Constant as u8);
2914 assert_eq!(chunk.constants[0], VMValue::Bool(false));
2915 }
2916
2917 #[test]
2918 fn compile_null() {
2919 let chunk = compile("null");
2920 assert_eq!(chunk.code[0], OpCode::Constant as u8);
2922 assert_eq!(chunk.constants[0], VMValue::Null);
2923 }
2924
2925 #[test]
2926 fn compile_string() {
2927 let chunk = compile(r#""hello""#);
2928 assert_eq!(chunk.constants[0], VMValue::String("hello".to_string()));
2929 }
2930
2931 #[test]
2932 fn compile_addition() {
2933 let chunk = compile("1 + 2");
2934 assert_eq!(chunk.constants[0], VMValue::Int(3));
2936 assert!(!chunk.code.contains(&(OpCode::Add as u8)));
2937 }
2938
2939 #[test]
2940 fn compile_addition_non_foldable() {
2941 let chunk = compile("let x = 1; in x + 2");
2943 assert!(chunk.code.contains(&(OpCode::Add as u8)));
2944 }
2945
2946 #[test]
2947 fn compile_if_else() {
2948 let chunk = compile("if true then 1 else 2");
2949 assert_eq!(chunk.constants[0], VMValue::Int(1));
2951 assert!(!chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2952 }
2953
2954 #[test]
2955 fn compile_if_else_non_foldable() {
2956 let chunk = compile("let b = true; in if b then 1 else 2");
2958 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2959 }
2960
2961 #[test]
2962 fn compile_list() {
2963 let chunk = compile("[1 2 3]");
2964 assert!(chunk.code.contains(&(OpCode::MakeList as u8)));
2965 }
2966
2967 #[test]
2968 fn compile_attrset() {
2969 let chunk = compile("{ a = 1; b = 2; }");
2970 assert!(chunk.code.contains(&(OpCode::MakeAttrs as u8)));
2971 }
2972
2973 #[test]
2974 fn compile_select() {
2975 let chunk = compile("{ a = 1; }.a");
2976 assert!(chunk.code.contains(&(OpCode::GetAttr as u8)));
2977 }
2978
2979 #[test]
2980 fn compile_lambda() {
2981 let chunk = compile("x: x + 1");
2982 assert!(chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))));
2984 }
2985
2986 #[test]
2987 fn compile_negate() {
2988 let chunk = compile("-42");
2989 assert_eq!(chunk.constants[0], VMValue::Int(-42));
2991 assert!(!chunk.code.contains(&(OpCode::Negate as u8)));
2992 }
2993
2994 #[test]
2995 fn compile_negate_non_foldable() {
2996 let chunk = compile("let x = 42; in -x");
2997 assert!(chunk.code.contains(&(OpCode::Negate as u8)));
2998 }
2999
3000 #[test]
3001 fn compile_not() {
3002 let chunk = compile("!true");
3003 assert_eq!(chunk.constants[0], VMValue::Bool(false));
3005 assert!(!chunk.code.contains(&(OpCode::Not as u8)));
3006 }
3007
3008 #[test]
3009 fn compile_assert() {
3010 let chunk = compile("assert true; 42");
3011 assert!(chunk.code.contains(&(OpCode::Assert as u8)));
3012 }
3013
3014 #[test]
3015 fn compile_let_in() {
3016 let chunk = compile("let x = 1; y = 2; in x + y");
3017 assert!(chunk.code.contains(&(OpCode::GetLocal as u8)));
3018 }
3019
3020 #[test]
3021 fn compile_parse_error() {
3022 let result = Compiler::compile("let in");
3023 assert!(result.is_err());
3024 }
3025
3026 #[test]
3027 fn compile_comparison() {
3028 let chunk = compile("1 < 2");
3029 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3031 }
3032
3033 #[test]
3034 fn compile_equality() {
3035 let chunk = compile("1 == 1");
3036 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3038 }
3039
3040 #[test]
3041 fn compile_update_attrs() {
3042 let chunk = compile("{ a = 1; } // { b = 2; }");
3043 assert!(chunk.code.contains(&(OpCode::UpdateAttrs as u8)));
3044 }
3045
3046 #[test]
3047 fn compile_list_concat() {
3048 let chunk = compile("[1] ++ [2]");
3049 assert!(chunk.code.contains(&(OpCode::Concat as u8)));
3050 }
3051
3052 #[test]
3053 fn compile_and_short_circuit() {
3054 let chunk = compile("true && false");
3055 assert_eq!(chunk.constants[0], VMValue::Bool(false));
3057 }
3058
3059 #[test]
3060 fn compile_and_short_circuit_non_foldable() {
3061 let chunk = compile("let a = true; in a && false");
3062 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3063 }
3064
3065 #[test]
3066 fn compile_or_short_circuit() {
3067 let chunk = compile("false || true");
3068 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3070 }
3071
3072 #[test]
3073 fn compile_or_short_circuit_non_foldable() {
3074 let chunk = compile("let a = false; in a || true");
3075 assert!(chunk.code.contains(&(OpCode::JumpIfTrue as u8)));
3076 }
3077
3078 #[test]
3079 fn compile_has_attr() {
3080 let chunk = compile("{ a = 1; } ? a");
3081 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3082 }
3083
3084 #[test]
3085 fn compile_select_or_default() {
3086 let chunk = compile("{ a = 1; }.b or 0");
3089 assert!(chunk.code.contains(&(OpCode::Dup as u8)));
3090 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3091 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3092 assert!(chunk.code.contains(&(OpCode::GetAttr as u8)));
3093 }
3094
3095 #[test]
3096 fn compile_dyn_select_or_default() {
3097 let chunk = compile(r#"let x = "a"; in { a = 1; }.${ x } or 0"#);
3100 assert!(chunk.code.contains(&(OpCode::Dup as u8)));
3101 assert!(chunk.code.contains(&(OpCode::DynHasAttr as u8)));
3102 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3103 assert!(chunk.code.contains(&(OpCode::DynGetAttr as u8)));
3105 }
3106
3107 #[test]
3108 fn compile_multi_segment_select_or_default() {
3109 let chunk = compile("{ a = { b = 1; }; }.a.b.c or 0");
3111 let has_attr_count = chunk.code.iter().filter(|&&b| b == OpCode::HasAttr as u8).count();
3113 assert!(has_attr_count >= 3, "expected >= 3 HasAttr ops for 3 segments, got {has_attr_count}");
3114 }
3115
3116 #[test]
3117 fn compile_pattern_lambda() {
3118 let chunk = compile("{ a, b }: a + b");
3119 assert!(chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))));
3120 }
3121
3122 #[test]
3123 fn compile_string_interpolation() {
3124 let chunk = compile(r#"let x = "world"; in "hello ${x}""#);
3125 assert!(chunk.code.contains(&(OpCode::Interpolate as u8)));
3127 }
3128
3129 #[test]
3132 fn detect_trivial_self_reference() {
3133 let root = rnix::Root::parse("x");
3134 let expr = root.tree().expr().unwrap();
3135 let bindings = vec![("x".to_string(), &expr)];
3136 let warnings = detect_trivial_cycles(&bindings);
3137 assert_eq!(warnings.len(), 1);
3138 assert!(warnings[0].contains("directly references itself"));
3139 }
3140
3141 #[test]
3142 fn detect_no_false_positive() {
3143 let root = rnix::Root::parse("y");
3144 let expr = root.tree().expr().unwrap();
3145 let bindings = vec![("x".to_string(), &expr)];
3146 let warnings = detect_trivial_cycles(&bindings);
3147 assert!(warnings.is_empty());
3148 }
3149
3150 #[test]
3151 fn detect_non_ident_no_warning() {
3152 let root = rnix::Root::parse("1 + 2");
3153 let expr = root.tree().expr().unwrap();
3154 let bindings = vec![("x".to_string(), &expr)];
3155 let warnings = detect_trivial_cycles(&bindings);
3156 assert!(warnings.is_empty());
3157 }
3158
3159 #[test]
3160 fn detect_trivial_cycles_multiple() {
3161 let root_x = rnix::Root::parse("x");
3162 let expr_x = root_x.tree().expr().unwrap();
3163 let root_y = rnix::Root::parse("y");
3164 let expr_y = root_y.tree().expr().unwrap();
3165 let root_z = rnix::Root::parse("1");
3166 let expr_z = root_z.tree().expr().unwrap();
3167 let bindings = vec![
3168 ("x".to_string(), &expr_x),
3169 ("y".to_string(), &expr_y),
3170 ("z".to_string(), &expr_z),
3171 ];
3172 let warnings = detect_trivial_cycles(&bindings);
3173 assert_eq!(warnings.len(), 2);
3174 }
3175
3176 fn nix_path_lock() -> std::sync::MutexGuard<'static, ()> {
3195 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
3196 LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
3197 }
3198
3199 #[test]
3200 fn path_search_compiles_with_matching_nix_path() {
3201 let _nix_path = nix_path_lock();
3202 let dir = tempfile::tempdir().unwrap();
3205 let target = dir.path().join("mypkg");
3206 std::fs::create_dir(&target).unwrap();
3207 let nix_path_val = format!("mypkg={}", target.display());
3209 unsafe { std::env::set_var("NIX_PATH", &nix_path_val) };
3211 let result = Compiler::compile("<mypkg>");
3212 unsafe { std::env::remove_var("NIX_PATH") };
3213 assert!(result.is_ok(), "expected compile success, got: {result:?}");
3214 let (chunk, _) = result.unwrap();
3215 assert!(
3217 chunk.constants.iter().any(|c| matches!(c, VMValue::Path(p) if p == &target.display().to_string())),
3218 "expected path constant for {:?}, got: {:?}",
3219 target.display(),
3220 chunk.constants,
3221 );
3222 }
3223
3224 #[test]
3225 fn path_search_fails_when_nix_path_no_match() {
3226 let _nix_path = nix_path_lock();
3227 unsafe { std::env::set_var("NIX_PATH", "other=/nonexistent") };
3230 let result = Compiler::compile("<nosuchpkg>");
3231 unsafe { std::env::remove_var("NIX_PATH") };
3232
3233 assert!(
3248 result.is_ok(),
3249 "an unresolvable search path is deferred to force-time, not a \
3250 compile error; got: {result:?}"
3251 );
3252 let (chunk, _) = result.unwrap();
3253 assert!(
3254 chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))),
3255 "expected a deferred-throw closure in the constant pool, got: {:?}",
3256 chunk.constants,
3257 );
3258 }
3259
3260 #[test]
3261 fn path_search_with_sub_path() {
3262 let _nix_path = nix_path_lock();
3263 let dir = tempfile::tempdir().unwrap();
3265 let nixpkgs = dir.path().join("nixpkgs-src");
3266 let lib_dir = nixpkgs.join("lib");
3267 std::fs::create_dir_all(&lib_dir).unwrap();
3268 let nix_path_val = format!("nixpkgs={}", nixpkgs.display());
3269 unsafe { std::env::set_var("NIX_PATH", &nix_path_val) };
3271 let result = Compiler::compile("<nixpkgs/lib>");
3272 unsafe { std::env::remove_var("NIX_PATH") };
3273 assert!(result.is_ok(), "expected compile success for sub-path, got: {result:?}");
3274 let (chunk, _) = result.unwrap();
3275 let expected_path = lib_dir.display().to_string();
3276 assert!(
3277 chunk.constants.iter().any(|c| matches!(c, VMValue::Path(p) if p == &expected_path)),
3278 "expected path constant for {expected_path}, got: {:?}",
3279 chunk.constants,
3280 );
3281 }
3282
3283 #[test]
3286 fn lambda_body_apply_emits_tail_call() {
3287 let chunk = compile("x: x 1");
3289 let closure_chunk = chunk
3292 .constants
3293 .iter()
3294 .find_map(|c| match c {
3295 VMValue::Closure(cl) => Some(&cl.chunk),
3296 _ => None,
3297 })
3298 .expect("expected a closure constant");
3299 assert!(
3300 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3301 "lambda body call should emit TailCall, bytecode: {:?}",
3302 closure_chunk.code,
3303 );
3304 }
3305
3306 #[test]
3307 fn if_then_apply_emits_tail_call() {
3308 let chunk = compile("x: if true then x 1 else 0");
3310 let closure_chunk = chunk
3311 .constants
3312 .iter()
3313 .find_map(|c| match c {
3314 VMValue::Closure(cl) => Some(&cl.chunk),
3315 _ => None,
3316 })
3317 .expect("expected a closure constant");
3318 assert!(
3319 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3320 "if-then call should emit TailCall, bytecode: {:?}",
3321 closure_chunk.code,
3322 );
3323 }
3324
3325 #[test]
3326 fn if_else_apply_emits_tail_call() {
3327 let chunk = compile("x: if false then 0 else x 1");
3329 let closure_chunk = chunk
3330 .constants
3331 .iter()
3332 .find_map(|c| match c {
3333 VMValue::Closure(cl) => Some(&cl.chunk),
3334 _ => None,
3335 })
3336 .expect("expected a closure constant");
3337 assert!(
3338 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3339 "if-else call should emit TailCall, bytecode: {:?}",
3340 closure_chunk.code,
3341 );
3342 }
3343
3344 #[test]
3345 fn non_tail_apply_emits_regular_call() {
3346 let chunk = compile("let f = x: x; in f (f 1)");
3349 assert!(
3352 chunk.code.contains(&(OpCode::Call as u8))
3353 || chunk.code.contains(&(OpCode::GetLocalCall as u8)),
3354 "non-tail call should emit Call or GetLocalCall, bytecode: {:?}",
3355 chunk.code,
3356 );
3357 }
3358
3359 #[test]
3360 fn assert_body_apply_emits_tail_call() {
3361 let chunk = compile("f: assert true; f 1");
3363 let closure_chunk = chunk
3364 .constants
3365 .iter()
3366 .find_map(|c| match c {
3367 VMValue::Closure(cl) => Some(&cl.chunk),
3368 _ => None,
3369 })
3370 .expect("expected a closure constant");
3371 assert!(
3372 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3373 "assert body call should emit TailCall, bytecode: {:?}",
3374 closure_chunk.code,
3375 );
3376 }
3377
3378 #[test]
3381 fn multi_segment_hasattr_compiles() {
3382 let chunk = compile("{ a = { b = 1; }; } ? a");
3384 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3385 }
3386
3387 #[test]
3388 fn single_segment_hasattr_still_works() {
3389 let chunk = compile("{ x = 1; } ? x");
3391 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3392 }
3393
3394 #[test]
3395 fn multi_segment_hasattr_deep_path() {
3396 let chunk = compile("{ a = { b = 1; }; } ? a.b");
3398 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3400 }
3401}