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