snix_eval/compiler/bindings.rs
1//! This module implements compiler logic related to name/value binding
2//! definitions (that is, attribute sets and let-expressions).
3//!
4//! In the case of recursive scopes these cases share almost all of their
5//! (fairly complex) logic.
6
7use std::iter::Peekable;
8
9use rnix::ast::HasEntry;
10use rowan::ast::AstChildren;
11
12use crate::spans::{EntireFile, OrEntireFile};
13
14use super::*;
15
16type PeekableAttrs = Peekable<AstChildren<ast::Attr>>;
17
18/// What kind of bindings scope is being compiled?
19#[derive(Clone, Copy, PartialEq)]
20enum BindingsKind {
21 /// Standard `let ... in ...`-expression.
22 LetIn,
23
24 /// Non-recursive attribute set.
25 Attrs,
26
27 /// Recursive attribute set.
28 RecAttrs,
29}
30
31impl BindingsKind {
32 fn is_attrs(&self) -> bool {
33 matches!(self, BindingsKind::Attrs | BindingsKind::RecAttrs)
34 }
35}
36
37// Internal representation of an attribute set used for merging sets, or
38// inserting nested keys.
39#[derive(Clone)]
40struct AttributeSet {
41 /// Original span at which this set was first encountered.
42 span: Span,
43
44 /// Tracks the kind of set (rec or not).
45 kind: BindingsKind,
46
47 /// All inherited entries
48 inherits: Vec<ast::Inherit>,
49
50 /// All internal entries
51 entries: Vec<(Span, PeekableAttrs, ast::Expr)>,
52}
53
54impl ToSpan for AttributeSet {
55 fn span_for(&self, _: &codemap::File) -> Span {
56 self.span
57 }
58}
59
60impl AttributeSet {
61 fn from_ast(c: &Compiler, node: &ast::AttrSet) -> Self {
62 AttributeSet {
63 span: c.span_for(node),
64
65 // Kind of the attrs depends on the first time it is
66 // encountered. We actually believe this to be a Nix
67 // bug: https://github.com/NixOS/nix/issues/7111
68 kind: if node.rec_token().is_some() {
69 BindingsKind::RecAttrs
70 } else {
71 BindingsKind::Attrs
72 },
73
74 inherits: ast::HasEntry::inherits(node).collect(),
75
76 entries: ast::HasEntry::attrpath_values(node)
77 .map(|entry| {
78 let span = c.span_for(&entry);
79 (
80 span,
81 entry.attrpath().unwrap().attrs().peekable(),
82 entry.value().unwrap(),
83 )
84 })
85 .collect(),
86 }
87 }
88}
89
90// Data structures to track the bindings observed in the second pass, and
91// forward the information needed to compile their value.
92enum Binding {
93 InheritFrom {
94 namespace: ast::Expr,
95 name: SmolStr,
96 span: Span,
97 },
98
99 Plain {
100 expr: ast::Expr,
101 },
102
103 Set(AttributeSet),
104}
105
106impl Binding {
107 /// Merge the provided value into the current binding, or emit an
108 /// error if this turns out to be impossible.
109 fn merge(
110 &mut self,
111 c: &mut Compiler,
112 span: Span,
113 mut remaining_path: PeekableAttrs,
114 value: ast::Expr,
115 ) {
116 match self {
117 Binding::InheritFrom { name, ref span, .. } => {
118 c.emit_error(span, ErrorKind::UnmergeableInherit { name: name.clone() })
119 }
120
121 // If the value is not yet a nested binding, flip the representation
122 // and recurse.
123 Binding::Plain { expr } => match expr {
124 ast::Expr::AttrSet(existing) => {
125 let nested = AttributeSet::from_ast(c, existing);
126 *self = Binding::Set(nested);
127 self.merge(c, span, remaining_path, value);
128 }
129
130 _ => c.emit_error(&value, ErrorKind::UnmergeableValue),
131 },
132
133 // If the value is nested further, it is simply inserted into the
134 // bindings with its full path and resolved recursively further
135 // down.
136 Binding::Set(existing) if remaining_path.peek().is_some() => {
137 existing.entries.push((span, remaining_path, value))
138 }
139
140 Binding::Set(existing) => {
141 if let ast::Expr::AttrSet(new) = value {
142 existing.inherits.extend(ast::HasEntry::inherits(&new));
143 existing
144 .entries
145 .extend(ast::HasEntry::attrpath_values(&new).map(|entry| {
146 let span = c.span_for(&entry);
147 (
148 span,
149 entry.attrpath().unwrap().attrs().peekable(),
150 entry.value().unwrap(),
151 )
152 }));
153 } else {
154 // This branch is unreachable because in cases where the
155 // path is empty (i.e. there is no further nesting), the
156 // previous try_merge function already verified that the
157 // expression is an attribute set.
158
159 // TODO(tazjin): Consider making this branch live by
160 // shuffling that check around and emitting a static error
161 // here instead of a runtime error.
162 unreachable!()
163 }
164 }
165 }
166 }
167}
168
169enum KeySlot {
170 /// There is no key slot (`let`-expressions do not emit their key).
171 None { name: SmolStr },
172
173 /// The key is statically known and has a slot.
174 Static { slot: LocalIdx, name: SmolStr },
175
176 /// The key is dynamic, i.e. only known at runtime, and must be compiled
177 /// into its slot.
178 Dynamic { slot: LocalIdx, attr: ast::Attr },
179}
180
181struct TrackedBinding {
182 key_slot: KeySlot,
183 value_slot: LocalIdx,
184 binding: Binding,
185}
186
187impl TrackedBinding {
188 /// Does this binding match the given key?
189 ///
190 /// Used to determine which binding to merge another one into.
191 fn matches(&self, key: &str) -> bool {
192 match &self.key_slot {
193 KeySlot::None { name } => name == key,
194 KeySlot::Static { name, .. } => name == key,
195 KeySlot::Dynamic { .. } => false,
196 }
197 }
198}
199
200struct TrackedBindings {
201 bindings: Vec<TrackedBinding>,
202}
203
204impl TrackedBindings {
205 fn new() -> Self {
206 TrackedBindings { bindings: vec![] }
207 }
208
209 /// Attempt to merge an entry into an existing matching binding, assuming
210 /// that the provided binding is mergable (i.e. either a nested key or an
211 /// attribute set literal).
212 ///
213 /// Returns true if the binding was merged, false if it needs to be compiled
214 /// separately as a new binding.
215 fn try_merge(
216 &mut self,
217 c: &mut Compiler,
218 span: Span,
219 name: &ast::Attr,
220 mut remaining_path: PeekableAttrs,
221 value: ast::Expr,
222 ) -> bool {
223 // If the path has no more entries, and if the entry is not an
224 // attribute set literal, the entry can not be merged.
225 if remaining_path.peek().is_none() && !matches!(value, ast::Expr::AttrSet(_)) {
226 return false;
227 }
228
229 // If the first element of the path is not statically known, the entry
230 // can not be merged.
231 let name = match expr_static_attr_str(name) {
232 Some(name) => name,
233 None => return false,
234 };
235
236 // If there is no existing binding with this key, the entry can not be
237 // merged.
238 // TODO: benchmark whether using a map or something is useful over the
239 // `find` here
240 let binding = match self.bindings.iter_mut().find(|b| b.matches(&name)) {
241 Some(b) => b,
242 None => return false,
243 };
244
245 // No more excuses ... the binding can be merged!
246 binding.binding.merge(c, span, remaining_path, value);
247
248 true
249 }
250
251 /// Add a completely new binding to the tracked bindings.
252 fn track_new(&mut self, key_slot: KeySlot, value_slot: LocalIdx, binding: Binding) {
253 self.bindings.push(TrackedBinding {
254 key_slot,
255 value_slot,
256 binding,
257 });
258 }
259}
260
261/// Wrapper around the `ast::HasEntry` trait as that trait can not be
262/// implemented for custom types.
263trait HasEntryProxy {
264 fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>>;
265
266 fn attributes<'a>(
267 &self,
268 file: &'a codemap::File,
269 ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a>;
270}
271
272impl<N: HasEntry> HasEntryProxy for N {
273 fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
274 Box::new(ast::HasEntry::inherits(self))
275 }
276
277 fn attributes<'a>(
278 &self,
279 file: &'a codemap::File,
280 ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
281 Box::new(ast::HasEntry::attrpath_values(self).map(move |entry| {
282 (
283 entry.span_for(file),
284 entry.attrpath().unwrap().attrs().peekable(),
285 entry.value().unwrap(),
286 )
287 }))
288 }
289}
290
291impl HasEntryProxy for AttributeSet {
292 fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
293 Box::new(self.inherits.clone().into_iter())
294 }
295
296 fn attributes<'a>(
297 &self,
298 _: &'a codemap::File,
299 ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
300 Box::new(self.entries.clone().into_iter())
301 }
302}
303
304/// AST-traversing functions related to bindings.
305impl Compiler<'_, '_> {
306 /// Compile all inherits of a node with entries that do *not* have a
307 /// namespace to inherit from, and return the remaining ones that do.
308 fn compile_plain_inherits<N>(
309 &mut self,
310 slot: LocalIdx,
311 kind: BindingsKind,
312 count: &mut usize,
313 node: &N,
314 ) -> Vec<(ast::Expr, SmolStr, Span)>
315 where
316 N: ToSpan + HasEntryProxy,
317 {
318 // Pass over all inherits, resolving only those without namespaces.
319 // Since they always resolve in a higher scope, we can just compile and
320 // declare them immediately.
321 //
322 // Inherits with namespaces are returned to the caller.
323 let mut inherit_froms: Vec<(ast::Expr, SmolStr, Span)> = vec![];
324
325 for inherit in node.inherits() {
326 if inherit.attrs().peekable().peek().is_none() {
327 self.emit_warning(&inherit, WarningKind::EmptyInherit);
328 continue;
329 }
330
331 match inherit.from() {
332 // Within a `let` binding, inheriting from the outer scope is a
333 // no-op *if* there are no dynamic bindings.
334 None if !kind.is_attrs() && !self.has_dynamic_ancestor() => {
335 self.emit_warning(&inherit, WarningKind::UselessInherit);
336 continue;
337 }
338
339 None => {
340 for attr in inherit.attrs() {
341 let name = match expr_static_attr_str(&attr) {
342 Some(name) => name,
343 None => {
344 self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
345 continue;
346 }
347 };
348
349 // If the identifier resolves statically in a `let`, it
350 // has precedence over dynamic bindings, and the inherit
351 // is useless.
352 if kind == BindingsKind::LetIn
353 && matches!(
354 self.scope_mut().resolve_local(&name),
355 LocalPosition::Known(_)
356 )
357 {
358 self.emit_warning(&attr, WarningKind::UselessInherit);
359 continue;
360 }
361
362 *count += 1;
363
364 // Place key on the stack when compiling attribute sets.
365 if kind.is_attrs() {
366 self.emit_constant(name.as_str().into(), &attr);
367 let span = self.span_for(&attr);
368 self.scope_mut().declare_phantom(span, true);
369 }
370
371 // Place the value on the stack. Note that because plain
372 // inherits are always in the outer scope, the slot of
373 // *this* scope itself is used.
374 self.compile_identifier_access(slot, &name, &attr);
375
376 // In non-recursive attribute sets, the key slot must be
377 // a phantom (i.e. the identifier can not be resolved in
378 // this scope).
379 let idx = if kind == BindingsKind::Attrs {
380 let span = self.span_for(&attr);
381 self.scope_mut().declare_phantom(span, false)
382 } else {
383 self.declare_local(&attr, name)
384 };
385
386 self.scope_mut().mark_initialised(idx);
387 }
388 }
389
390 Some(from) => {
391 for attr in inherit.attrs() {
392 let name = match expr_static_attr_str(&attr) {
393 Some(name) => name,
394 None => {
395 self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
396 continue;
397 }
398 };
399
400 *count += 1;
401 inherit_froms.push((from.expr().unwrap(), name, self.span_for(&attr)));
402 }
403 }
404 }
405 }
406
407 inherit_froms
408 }
409
410 /// Declare all namespaced inherits, that is inherits which are inheriting
411 /// values from an attribute set.
412 ///
413 /// This only ensures that the locals stack is aware of the inherits, it
414 /// does not yet emit bytecode that places them on the stack. This is up to
415 /// the owner of the `bindings` vector, which this function will populate.
416 fn declare_namespaced_inherits(
417 &mut self,
418 kind: BindingsKind,
419 inherit_froms: Vec<(ast::Expr, SmolStr, Span)>,
420 bindings: &mut TrackedBindings,
421 ) {
422 for (from, name, span) in inherit_froms {
423 let key_slot = if kind.is_attrs() {
424 // In an attribute set, the keys themselves are placed on the
425 // stack but their stack slot is inaccessible (it is only
426 // consumed by `OpAttrs`).
427 KeySlot::Static {
428 slot: self.scope_mut().declare_phantom(span, false),
429 name: name.clone(),
430 }
431 } else {
432 KeySlot::None { name: name.clone() }
433 };
434
435 let value_slot = match kind {
436 // In recursive scopes, the value needs to be accessible on the
437 // stack.
438 BindingsKind::LetIn | BindingsKind::RecAttrs => {
439 self.declare_local(&span, name.clone())
440 }
441
442 // In non-recursive attribute sets, the value is inaccessible
443 // (only consumed by `OpAttrs`).
444 BindingsKind::Attrs => self.scope_mut().declare_phantom(span, false),
445 };
446
447 bindings.track_new(
448 key_slot,
449 value_slot,
450 Binding::InheritFrom {
451 namespace: from,
452 name,
453 span,
454 },
455 );
456 }
457 }
458
459 /// Declare all regular bindings (i.e. `key = value;`) in a bindings scope,
460 /// but do not yet compile their values.
461 fn declare_bindings<N>(
462 &mut self,
463 kind: BindingsKind,
464 count: &mut usize,
465 bindings: &mut TrackedBindings,
466 node: &N,
467 ) where
468 N: ToSpan + HasEntryProxy,
469 {
470 for (span, mut path, value) in node.attributes(self.file) {
471 let key = path.next().unwrap();
472
473 if bindings.try_merge(self, span, &key, path.clone(), value.clone()) {
474 // Binding is nested, or already exists and was merged, move on.
475 continue;
476 }
477
478 *count += 1;
479
480 let key_span = self.span_for(&key);
481 let key_slot = match expr_static_attr_str(&key) {
482 Some(name) if kind.is_attrs() => KeySlot::Static {
483 name,
484 slot: self.scope_mut().declare_phantom(key_span, false),
485 },
486
487 Some(name) => KeySlot::None { name },
488
489 None if kind.is_attrs() => KeySlot::Dynamic {
490 attr: key,
491 slot: self.scope_mut().declare_phantom(key_span, false),
492 },
493
494 None => {
495 self.emit_error(&key, ErrorKind::DynamicKeyInScope("let-expression"));
496 continue;
497 }
498 };
499
500 let value_slot = match kind {
501 BindingsKind::LetIn | BindingsKind::RecAttrs => match &key_slot {
502 // In recursive scopes, the value needs to be accessible on the
503 // stack if it is statically known
504 KeySlot::None { name } | KeySlot::Static { name, .. } => {
505 self.declare_local(&key_span, name.as_str())
506 }
507
508 // Dynamic values are never resolvable (as their names are
509 // of course only known at runtime).
510 //
511 // Note: This branch is unreachable in `let`-expressions.
512 KeySlot::Dynamic { .. } => self.scope_mut().declare_phantom(key_span, false),
513 },
514
515 // In non-recursive attribute sets, the value is inaccessible
516 // (only consumed by `OpAttrs`).
517 BindingsKind::Attrs => self.scope_mut().declare_phantom(key_span, false),
518 };
519
520 let binding = if path.peek().is_some() {
521 Binding::Set(AttributeSet {
522 span,
523 kind: BindingsKind::Attrs,
524 inherits: vec![],
525 entries: vec![(span, path, value)],
526 })
527 } else {
528 Binding::Plain { expr: value }
529 };
530
531 bindings.track_new(key_slot, value_slot, binding);
532 }
533 }
534
535 /// Compile attribute set literals into equivalent bytecode.
536 ///
537 /// This is complicated by a number of features specific to Nix attribute
538 /// sets, most importantly:
539 ///
540 /// 1. Keys can be dynamically constructed through interpolation.
541 /// 2. Keys can refer to nested attribute sets.
542 /// 3. Attribute sets can (optionally) be recursive.
543 pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: &ast::AttrSet) {
544 // Open a scope to track the positions of the temporaries used by the
545 // `OpAttrs` instruction.
546 self.scope_mut().begin_scope();
547
548 let kind = if node.rec_token().is_some() {
549 BindingsKind::RecAttrs
550 } else {
551 BindingsKind::Attrs
552 };
553
554 self.compile_bindings(slot, kind, node);
555
556 // Remove the temporary scope, but do not emit any additional cleanup
557 // (OpAttrs consumes all of these locals).
558 self.scope_mut().end_scope();
559 }
560
561 /// Emit definitions for all variables in the top-level global env passed to the evaluation (eg
562 /// local variables in the REPL)
563 pub(super) fn compile_env(&mut self, env: &FxHashMap<SmolStr, Value>) {
564 for (name, value) in env {
565 self.scope_mut().declare_constant(name.to_string());
566 self.emit_constant(value.clone(), &EntireFile);
567 }
568 }
569
570 /// Actually binds all tracked bindings by emitting the bytecode that places
571 /// them in their stack slots.
572 fn bind_values(&mut self, bindings: TrackedBindings) {
573 let mut value_indices: Vec<LocalIdx> = vec![];
574
575 for binding in bindings.bindings.into_iter() {
576 value_indices.push(binding.value_slot);
577
578 match binding.key_slot {
579 KeySlot::None { .. } => {} // nothing to do here
580
581 KeySlot::Static { slot, name } => {
582 let span = self.scope()[slot].span;
583 self.emit_constant(name.as_str().into(), &OrEntireFile(span));
584 self.scope_mut().mark_initialised(slot);
585 }
586
587 KeySlot::Dynamic { slot, attr } => {
588 self.compile_attr(slot, &attr);
589 self.scope_mut().mark_initialised(slot);
590 }
591 }
592
593 match binding.binding {
594 // This entry is an inherit (from) expr. The value is placed on
595 // the stack by selecting an attribute.
596 Binding::InheritFrom {
597 namespace,
598 name,
599 span,
600 } => {
601 // Create a thunk wrapping value (which may be one as well)
602 // to avoid forcing the from expr too early.
603 self.thunk(binding.value_slot, &namespace, |c, s| {
604 c.compile(s, namespace.clone());
605 c.emit_force(&namespace);
606
607 c.emit_constant(name.as_str().into(), &span);
608 c.push_op(Op::AttrsSelect, &span);
609 })
610 }
611
612 // Binding is "just" a plain expression that needs to be
613 // compiled.
614 Binding::Plain { expr } => self.compile(binding.value_slot, expr),
615
616 // Binding is a merged or nested attribute set, and needs to be
617 // recursively compiled as another binding.
618 Binding::Set(set) => self.thunk(binding.value_slot, &set, |c, _| {
619 c.scope_mut().begin_scope();
620 c.compile_bindings(binding.value_slot, set.kind, &set);
621 c.scope_mut().end_scope();
622 }),
623 }
624
625 // Any code after this point will observe the value in the right
626 // stack slot, so mark it as initialised.
627 self.scope_mut().mark_initialised(binding.value_slot);
628 }
629
630 // Final pass to emit finaliser instructions if necessary.
631 for idx in value_indices {
632 if self.scope()[idx].needs_finaliser {
633 let stack_idx = self.scope().stack_index(idx);
634 let span = self.scope()[idx].span;
635 self.push_op(Op::Finalise, &OrEntireFile(span));
636 self.push_uvarint(stack_idx.0 as u64)
637 }
638 }
639 }
640
641 fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)
642 where
643 N: ToSpan + HasEntryProxy,
644 {
645 let mut count = 0;
646 self.scope_mut().begin_scope();
647
648 // Vector to track all observed bindings.
649 let mut bindings = TrackedBindings::new();
650
651 let inherit_froms = self.compile_plain_inherits(slot, kind, &mut count, node);
652 self.declare_namespaced_inherits(kind, inherit_froms, &mut bindings);
653 self.declare_bindings(kind, &mut count, &mut bindings, node);
654
655 // Check if we can bail out on empty bindings
656 if count == 0 {
657 // still need an attrset to exist, but it is empty.
658 if kind.is_attrs() {
659 self.emit_constant(Value::Attrs(Box::new(NixAttrs::empty())), node);
660 return;
661 }
662
663 self.emit_warning(node, WarningKind::EmptyLet);
664 return;
665 }
666
667 // Actually bind values and ensure they are on the stack.
668 self.bind_values(bindings);
669
670 if kind.is_attrs() {
671 self.push_op(Op::Attrs, node);
672 self.push_uvarint(count as u64);
673 }
674 }
675
676 /// Compile a standard `let ...; in ...` expression.
677 ///
678 /// Unless in a non-standard scope, the encountered values are simply pushed
679 /// on the stack and their indices noted in the entries vector.
680 pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &ast::LetIn) {
681 self.compile_bindings(slot, BindingsKind::LetIn, node);
682
683 // Deal with the body, then clean up the locals afterwards.
684 self.compile(slot, node.body().unwrap());
685 self.cleanup_scope(node);
686 }
687
688 pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &ast::LegacyLet) {
689 self.emit_warning(node, WarningKind::DeprecatedLegacyLet);
690 self.scope_mut().begin_scope();
691 self.compile_bindings(slot, BindingsKind::RecAttrs, node);
692
693 // Remove the temporary scope, but do not emit any additional cleanup
694 // (OpAttrs consumes all of these locals).
695 self.scope_mut().end_scope();
696
697 self.emit_constant("body".into(), node);
698 self.push_op(Op::AttrsSelect, node);
699 }
700
701 /// Is the given identifier defined *by the user* in any current scope?
702 pub(super) fn is_user_defined(&mut self, ident: &str) -> bool {
703 matches!(
704 self.scope_mut().resolve_local(ident),
705 LocalPosition::Known(_) | LocalPosition::Recursive(_)
706 )
707 }
708
709 /// Resolve and compile access to an identifier in the scope.
710 fn compile_identifier_access<N: ToSpan + Clone>(
711 &mut self,
712 slot: LocalIdx,
713 ident: &str,
714 node: &N,
715 ) {
716 match self.scope_mut().resolve_local(ident) {
717 LocalPosition::Unknown => {
718 // Are we possibly dealing with an upvalue?
719 if let Some(idx) = self.resolve_upvalue_for_use(self.contexts.len() - 1, ident) {
720 self.push_op(Op::GetUpvalue, node);
721 self.push_uvarint(idx.0 as u64);
722 return;
723 }
724
725 // Globals are the "upmost upvalues": they behave
726 // exactly like a `let ... in` prepended to the
727 // program's text, and the global scope is nothing
728 // more than the parent scope of the root scope.
729 if let Some(global) = self.globals.get(ident) {
730 self.emit_constant(global.clone(), &self.span_for(node));
731 return;
732 }
733
734 // If there is a non-empty `with`-stack (or a parent context
735 // with one), emit a runtime dynamic resolution instruction.
736 //
737 // Since it is possible for users to e.g. assign a variable to a
738 // dynamic resolution without actually using it, this operation
739 // is wrapped in an extra thunk.
740 if self.has_dynamic_ancestor() {
741 self.thunk(slot, node, |c, _| {
742 c.context_mut().captures_with_stack = true;
743 c.emit_constant(ident.into(), node);
744 c.push_op(Op::ResolveWith, node);
745 });
746 return;
747 }
748
749 // Otherwise, this variable is missing.
750 self.emit_error(node, ErrorKind::UnknownStaticVariable);
751 }
752
753 LocalPosition::Known(idx) => {
754 self.scope_mut().mark_used(idx);
755
756 let stack_idx = self.scope().stack_index(idx);
757 self.push_op(Op::GetLocal, node);
758 self.push_uvarint(stack_idx.0 as u64);
759 }
760
761 // This identifier is referring to a value from the same scope which
762 // is not yet defined. This identifier access must be thunked.
763 LocalPosition::Recursive(idx) => {
764 self.scope_mut().mark_used(idx);
765 self.thunk(slot, node, move |compiler, _| {
766 let upvalue_idx =
767 compiler.add_upvalue(compiler.contexts.len() - 1, UpvalueKind::Local(idx));
768 compiler.push_op(Op::GetUpvalue, node);
769 compiler.push_uvarint(upvalue_idx.0 as u64);
770 })
771 }
772 };
773 }
774
775 pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: &ast::Ident) {
776 let ident = node.ident_token().unwrap();
777 self.compile_identifier_access(slot, ident.text(), node);
778 }
779}
780
781/// Private compiler helpers related to bindings.
782impl Compiler<'_, '_> {
783 // ATTN: Also marks local backing the upvalue as used if any
784 fn resolve_upvalue_for_use(&mut self, ctx_idx: usize, name: &str) -> Option<UpvalueIdx> {
785 if ctx_idx == 0 {
786 // There can not be any upvalue at the outermost context.
787 return None;
788 }
789
790 // Determine whether the upvalue is a local in the enclosing context.
791 match self.contexts[ctx_idx - 1].scope.resolve_local(name) {
792 // recursive upvalues are dealt with the same way as standard known
793 // ones, as thunks and closures are guaranteed to be placed on the
794 // stack (i.e. in the right position) *during* their runtime
795 // construction
796 LocalPosition::Known(idx) | LocalPosition::Recursive(idx) => {
797 self.contexts[ctx_idx - 1].scope.mark_used(idx);
798 return Some(self.add_upvalue(ctx_idx, UpvalueKind::Local(idx)));
799 }
800
801 LocalPosition::Unknown => { /* continue below */ }
802 };
803
804 // If the upvalue comes from even further up, we need to recurse to make
805 // sure that the upvalues are created at each level.
806 if let Some(idx) = self.resolve_upvalue_for_use(ctx_idx - 1, name) {
807 return Some(self.add_upvalue(ctx_idx, UpvalueKind::Upvalue(idx)));
808 }
809
810 None
811 }
812
813 fn add_upvalue(&mut self, ctx_idx: usize, kind: UpvalueKind) -> UpvalueIdx {
814 // If there is already an upvalue closing over the specified index,
815 // retrieve that instead.
816 for (idx, existing) in self.contexts[ctx_idx].scope.upvalues.iter().enumerate() {
817 if existing.kind == kind {
818 return UpvalueIdx(idx);
819 }
820 }
821
822 self.contexts[ctx_idx].scope.upvalues.push(Upvalue { kind });
823
824 let idx = UpvalueIdx(self.contexts[ctx_idx].lambda.upvalue_count);
825 self.contexts[ctx_idx].lambda.upvalue_count += 1;
826 idx
827 }
828}